2016-04-21 25 views
0

我是一个线程新手,并试图使用SemaphoreSlim允许我同时运行一定数量的长期任务。C# - 错误传播与ContinueWith

我的挑战是,鉴于我写的方式,任何异常都没有被正确捕获。

这里是我当前的代码非常简单的例子:

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,我得到了“结束 - 我们不应该在这里得到”消息,而不是错误信息我本来想去的。

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

+0

[你不应该使用'StartNew'没有传递任务schedueller(HTTP://blog.stephencleary。 com/2013/08/startnew-is-dangerous.html),使用'Task.Run('而不是。也可以用整个行代替mor e高效'等待Task.Delay(3000)' –

+0

@ScottChamberlain,谢谢! - 我真的没有太注意这一点,因为我的观点是,我从来没有达到这一线 - 我抛出一个例外,旨在要求如何处理抛出的东西.... –

+1

我看到了,它仍然是一个非常糟糕的习惯,你应该尝试打破它。 [ContinueWith有同样的问题](http://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html),但这个问题不是你遇到的问题的原因。 –

回答

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 is dangerousRun取代StartNew(我形容我的博客)。


该代码仍然不完全正确。你面临的问题是:你想要异步并行还是并行并发?这一切都归结于FaultyAsyncTask.Run(() => Thread.Sleep(3000))行。

如果这是一个真正异步的占位符(例如,,I/O)操作,则ThreadTest应当异步和使用Task.WhenAll代替WaitAll,因为这样的:

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 
} 
+0

哇! - 完全是我正在寻找回答 - 谢谢你太多了! –

0

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

每微软的网站(https://msdn.microsoft.com/en-us/magazine/jj991977.aspx):

当一个异常被抛出一个异步任务或异步任务 方法,该异常被捕获并放置在任务对象

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

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

0

您可以将您的ThreadTest函数标记为异步并使用:await Faulty();在try-catch块内部,你将能够捕捉到异常。