2016-04-21 49 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塊內部,你將能夠捕捉到異常。