2014-04-07 70 views
2

我有一個名爲WaitForAction的方法,它接受一個Action委託並在新任務中執行它。該方法阻塞,直到任務完成或超時過期。它使用ManualResetEvent等待超時/完成。任務似乎互相阻塞

以下代碼顯示了在多線程環境中測試該方法的嘗試。

class Program 
{ 
    public static void Main() 
    { 
     List<Foo> list = new List<Foo>(); 

     for (int i = 0; i < 10; i++) 
     { 
      Foo foo = new Foo(); 
      list.Add(foo); 
      foo.Bar(); 
     } 

     SpinWait.SpinUntil(() => list.Count(f => f.finished || f.failed) == 10, 2000); 
     Debug.WriteLine(list.Count(f => f.finished)); 
    } 
} 

public class Foo 
{ 
    public volatile bool finished = false; 
    public volatile bool failed = false; 

    public void Bar() 
    { 
     Task.Factory.StartNew(() => 
     { 
      try 
      { 
       WaitForAction(1000,() => { }); 
       finished = true; 
      } 
      catch 
      { 
       failed = true; 
      } 
     }); 
    } 

    private void WaitForAction(int iMsToWait, Action action) 
    { 
     using (ManualResetEvent waitHandle = new ManualResetEvent(false)) 
     { 
      Task.Factory.StartNew(() => 
      { 
       action(); 
       waitHandle.SafeSet(); 
      }); 

      if (waitHandle.SafeWaitOne(iMsToWait) == false) 
      { 
       throw new Exception("Timeout"); 
      } 
     } 
    } 
} 

由於動作是什麼都不做我希望通過調用Foo.Bar 10倍的超時時間內完成好開始了10個任務。有時會發生這種情況,但通常程序需要2秒才能執行並報告只有2個Foo「完成」實例沒有錯誤。換句話說,8個對WaitForAction的調用已經超時。

我假設WaitForAction是線程安全的,因爲任務提供的線程上的每個調用都有自己的堆棧。我通過記錄線程ID和每個調用的等待句柄ID或多或少證明了這一點。

我意識到這個代碼是一個愚蠢的例子,但我對這個原理感興趣。任務調度程序是否有可能將運行該操作委託的任務調度到已在等待另一個操作完成的同一個線程池線程?還是還有其他事情我錯過了?

回答

2

Task.Factory默認使用ThreadPool。每撥打WaitHandle.WaitOne,都會阻止一個工作線程。 .Net 4/4.5線程池根據您的硬件平臺(例如,我的機器上的4個)開始少量工作線程,並定期重新評估池大小(我相信每隔1秒),創建新的工人如果有必要。

由於程序會阻塞所有工作線程,並且線程池的增長速度不夠快,所以您看到的等待處理超時。

爲了證實這一點,你可以:1)增加超時或2)通過添加以下行到你的程序的開始增加開始線程池的大小:

ThreadPool.SetMinThreads(32, 4); 

,那麼你應該看到超時不要發生。


我相信你的問題是比什麼都重要的學術,但你可以看到一個更好的執行任務超時機制here,例如中

var task = Task.Run(someAction); 
if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout))) 
    await task; 
else 
    throw new TimeoutException(); 
+0

謝謝。作品。我不知道線程池在按需分配新線程方面速度很慢。 –

+0

@RonIdaho正常的假設是,你將在工作線程上做有用的CPU密集型工作,而不是阻塞它。因此,初始大小對於CPU密集型工作來說通常是最佳的,分配新線程不會提高性能(甚至可能會影響性能)。 –