如何将异常从 ContinueWith 传播到无限循环任务的调用上下文

How to propagate an exception from ContinueWith to the calling context of an infinite loop task

我在任务中遇到无限循环。在某些情况下,此任务会抛出异常并终止。考虑以下代码片段。

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            int x = await FirstTask();
            window.Title = "FirstTask completed with " + x.ToString();
        }
        catch (ArgumentException ex)
        {
            textbox.Text = ex.Message;
        }
    }

    public async Task<int> FirstTask()
    {
        Task<int> secondTask;
        int result;

        secondTask = SecondTask();
        textbox.Text = "Awaiting SecondTask result";
        result = await secondTask;
        textbox.Text = result;

        secondTask.ContinueWith(async (Task t) =>
        {
             var thirdTask = ThirdTask();

             thirdTask.ContinueWith(
                async (m) =>
                     await Task.Run(() =>
                     {
                        throw thirdTask.Exception.InnerException;
                     }),
                TaskContinuationOptions.OnlyOnFaulted);

        }, TaskContinuationOptions.OnlyOnRanToCompletion);

        return 5;
    }

    public async Task<int> SecondTask()
    {
        await Task.Delay(1500);
        return 8;
    }

    public async Task ThirdTask()
    {
        while (true)
        {
            await Task.Delay(500);

            throw new ArgumentException("thirdException");
        }
    }

我的问题在于无法将从 ThirdTask 抛出的异常传播到 Button_Click 事件。显然,等待它不是一个选项,因为它是一个正在进行的无限操作(这只是为了快速失败而简化)。但是,如果仅在 ThirdTask 失败后才触发,则等待重新引发异常的 "short" 任务没有问题。请注意,我对 ThirdTask 的行为不感兴趣,除非它失败了,也就是说,当我能够在事件处理程序中等待 FirstTask 时。

实验表明,即使是最简单的示例也不会传播来自 ContinueWith 块的异常。

        private async void Button_Click(object sender, RoutedEventArgs e)
    {
        try
        {
            Task task = Task.Run(async () => { await Task.Delay(1000); });
            task.ContinueWith( (t) => { throw new ArgumentException("test"); }, TaskContinuationOptions.OnlyOnRanToCompletion);          
        }
        catch (ArgumentException ex)
        {
            textbox.Text = ex.Message;
        }
    }

那么,如果抛出异常的任务有一个无限循环,我无法等待它,我该如何将异常从 ContinueWith 传播到调用上下文?

我要解决的问题有两个: 首先,我需要初始化一个资源 (FirstTask),为此,我首先需要获取它 (SecondTask),然后用它开始一个进程 (ThirdTask),最后,初始化资源 (FirstTask) returns 一个指示资源状态的值,它不依赖于进程(ThirdTask)。进程 (ThirdTask) 重复调用另一个任务(在本例中为 Task.Delay)并对其执行一些工作,但它可能会失败。在这种情况下,它会抛出一个需要处理的异常。

第二部分是第二个代码示例的一般情况,即如何从 ContinueWith 中抛出异常以由调用上下文处理。

given that the task that throws it has an infinite loop, which prevents me from awaiting it?

这绝不会阻止您等待它。处理它抛出异常的情况的 [最简单] 方法是专门针对 await 它。

您可以简单地实现该方法:

public async Task FirstTask()
{
    Task<int> secondTask = SecondTask();
    textbox.Text = "Awaiting SecondTask result";
    textbox.Text = await secondTask;
    await ThirdTask();
}

如果点击处理程序既需要用第二次操作的结果更新文本框,又需要在第三次失败时更新 UI,那么您不需要将这两个操作都包装在 [=13= 中] 并直接从点击处理程序调用它们:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    try
    {
        textbox.Text = "Awaiting SecondTask result";
        int x = await SecondTask();
        window.Title = "SecondTask completed with " + x.ToString();
        await ThirdTask();
    }
    catch (ArgumentException ex)
    {
        textbox.Text = ex.Message;
    }
}