C# - 使用ContinueWith进行错误传播

时间:2016-04-21 13:32:13

标签: c# exception asynchronous task semaphore

我是一名线程新手,我正在尝试使用SemaphoreSlim来允许我同时运行一定数量的长任务。

我的挑战是,鉴于我编写它的方式,任何例外都没有被正确捕获。

以下是我当前代码的一个非常简化的示例:

public void ThreadTest()
{
    try
    {
        var currentTasks = new List<Task>();
        SemaphoreSlim maxThread = new SemaphoreSlim(2);

        for (int i = 1; i < 5; ++i)
        {
            maxThread.Wait();

            var testTask = Faulty().ContinueWith(tsk => maxThread.Release());
            currentTasks.Add(testTask);
        }

        Task.WaitAll(currentTasks.ToArray());
        Debug.WriteLine("End - We shouldn't have gotten here");
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.ToString());
    }
}

private async Task Faulty()
{
    throw new Exception("Never reach the awaiter");
    await Task.Factory.StartNew(() => Thread.Sleep(3000));
}

而且,不幸的是,在那里ContinueWith,我进入了“结束 - 我们不应该在这里”的消息,而不是我想要的错误消息。

如何更新此代码才能正常运行?我再次道歉,如果这是完全错误的,这是一个新手尝试将我在网上找到的东西放在一起的东西 - 任何和所有正确做到这一点的建议真的很感激!!!

3 个答案:

答案 0 :(得分:3)

  

如何更新此代码才能正常运行?

非常简单:不要使用ContinueWith。请改用await

public void ThreadTest()
{
  try
  {
    var currentTasks = new List<Task>();
    SemaphoreSlim maxThread = new SemaphoreSlim(2);

    for (int i = 1; i < 5; ++i)
    {
      maxThread.Wait();

      var testTask = TestAsync(maxThread);
      currentTasks.Add(testTask);
    }

    Task.WaitAll(currentTasks.ToArray());
  }
  catch (Exception ex)
  {
    Debug.WriteLine(ex.ToString());
  }
}

private async Task TestAsync(SemaphoreSlim maxThread)
{
  try
  {
    await FaultyAsync();
  }
  finally
  {
    maxThread.Release();
  }
}

private async Task FaultyAsync()
{
  throw new Exception("Never reach the awaiter");
  await Task.Run(() => Thread.Sleep(3000));
}

我还做了一些其他更改:添加了Async后缀以跟随async naming convention,并将StartNew替换为Run,因为StartNew is dangerous(如我在我的博客上描述。)

代码仍然没有完全正确。你的问题是:你想要异步并发还是并行并发?这一切都归结为Task.Run(() => Thread.Sleep(3000))中的FaultyAsync行。

如果这是真正异步(例如,I / O)操作的占位符,则ThreadTest应该异步并使用Task.WhenAll而不是WaitAll,如下所示:< / p>

public async Task TestAsync()
{
  try
  {
    var currentTasks = new List<Task>();
    SemaphoreSlim throttle = new SemaphoreSlim(2); // Not "maxThread" since we're not dealing with threads anymore

    for (int i = 1; i < 5; ++i)
    {
      var testTask = TestAsync(throttle);
      currentTasks.Add(testTask);
    }

    await Task.WhenAll(currentTasks);
  }
  catch (Exception ex)
  {
    Debug.WriteLine(ex.ToString());
  }
}

private async Task TestAsync(SemaphoreSlim throttle)
{
  await throttle.WaitAsync();
  try
  {
    await FaultyAsync();
  }
  finally
  {
    maxThread.Release();
  }
}

private async Task FaultyAsync()
{
  throw new Exception("Never reach the awaiter");
  await Task.Delay(3000); // Naturally asynchronous operation
}

另一方面,如果Task.Run(() => Thread.Sleep(3000))是真正同步(例如CPU)操作的占位符,那么您应该使用更高级别的并行抽象,而不是手动创建自己的任务:

public void ThreadTest()
{
  try
  {
    var options = new ParallelOptions { MaxDegreeOfParallelism = 2 };
    Parallel.For(1, 5, options, i => Faulty());
  }
  catch (Exception ex)
  {
    Debug.WriteLine(ex.ToString());
  }
}

private void Faulty()
{
  throw new Exception("Never reach the work");
  Thread.Sleep(3000); // Naturally synchronous operation
}

答案 1 :(得分:0)

这与异步任务中如何处理异常有关。

根据Microsoft的网站(https://msdn.microsoft.com/en-us/magazine/jj991977.aspx):

  

从异步任务或异步任务中抛出异常时   方法,捕获异常并将其放在Task对象

这意味着当您在异步方法中抛出异常时,它应该获取该异常并将其放在任务对象本身上。它甚至继续在​​网站上给出一个例子,如果返回了一个Task对象,那么异常永远不会在主线程中抛出,因为它被放在Task对象上。

听起来您需要检查Task对象以查看它是否有效或包含异常。

答案 2 :(得分:0)

您可以将ThreadTest函数标记为异步并使用:await Faulty();在try-catch块中,你可以捕获异常。