2014-01-16 36 views
8

讓我們假設我有這個簡單的代碼片段:如何序列化異步/等待?

async void button_Click(object sender, RoutedEventArgs e) 
{ 
    await Task.Factory.StartNew(() => 
    { 
     Console.WriteLine("start"); 
     Thread.Sleep(5000); 
     Console.WriteLine("end"); 
    }); 
} 

顯然,每次我推該按鈕時,前面的任務仍然運行一個新的任務甚至開始。我將如何推遲任何新任務,直到所有先前的任務完成?

更多的細節:

在上面的例子中,每個新的任務是相同的前任務。但是,在原始環境中,任務順序很重要:參數可能會改變(我可以使用DateTime.Now.Ticks「模擬」它)。 這些任務應該按照他們「註冊」的順序執行。具體來說,我的程序會與串口設備通信。我之前使用BlockingCollection做了這個後臺線程。但是,這次有一個嚴格的請求/響應協議,如果可能,我想使用異步/等待。

可能的解決辦法:

我可以想像創建任務,並將它們存儲在列表中。但是,我將如何執行與要求相關的任務?或者我應該回到之前用過的基於線程的解決方案嗎?

+3

您仍然可以使用'ConcurrentQueue'或'BlockingCollection'。由於這是一個生產者 - 消費者的情況,後者應該是最合適的。 –

+0

當然。但正如我所看到的,我需要在隊列中等待並循環它。我會存儲「任務」或數據以創建任務嗎?我會在「Task」中循環隊列還是循環啓動「Task」?儘管如此,這並不是什麼大問題,但我希望有另一種更適合這種請求/響應模式的方式。 – JeffRSon

回答

6

您可以異步等待SemaphoreSlim,並在作業完成後將其釋放。不要忘記配置信號量初始值爲1

private static SemaphoreSlim semaphore = new SemaphoreSlim(1); 

private async static void DoSomethingAsync() 
{ 
    await semaphore.WaitAsync(); 
    try 
    { 
     await Task.Factory.StartNew(() => 
     { 
      Console.WriteLine("start"); 
      Thread.Sleep(5000); 
      Console.WriteLine("end"); 
     }); 
    } 
    finally 
    { 
     semaphore.Release(); 
    } 
} 

private static void Main(string[] args) 
{ 
    DoSomethingAsync(); 
    DoSomethingAsync(); 
    Console.Read(); 
} 
+0

不要忘了Try-finally並在finally子句中釋放semaphone!否則,如果在你的任務中拋出異常,它可能會導致用戶非常沮喪的行爲:) – Olivier

+0

@Olivier謝謝,更新非常明顯! –

+0

這正是我需要排隊的偶然併發訪問! – JeffRSon

8

我推薦使用SemaphoreSlim進行同步。但是,您想要avoid Task.Factory.StartNew(正如我在我的博客中解釋的)以及definitely avoid async void(正如我在MSDN文章中所解釋的)。

private SemaphoreSlim _mutex = new SemaphoreSlim(1); 
async void button_Click(object sender, RoutedEventArgs e) 
{ 
    await Task.Run(async() => 
    { 
    await _mutex.WaitAsync(); 
    try 
    { 
     Console.WriteLine("start"); 
     Thread.Sleep(5000); 
     Console.WriteLine("end"); 
    } 
    finally 
    { 
     _mutex.Release(); 
    } 
    }); 
} 
+0

謝謝你鏈接到你的文章!非常好!順便說一句,當我嘗試你的例子時,我不得不用'async'標記lambda表達式以便能夠編譯它。它是否正確? – JeffRSon

+0

@StephenCleary,是否有任何理由我們不能只是「等待」以前的任務,就像[this](http://stackoverflow.com/a/21178958/1768303)? – Noseratio

+2

@Noseratio:這種方法也適用。國際海事組織的「SemaphoreSlim」代碼有更明確的意圖;從'await'-之前的任務代碼中並不清楚,複製以前的任務非常重要。 –

2

我可能會錯過一些東西,但我不認爲SemaphoreSlim是OP的場景所需要的。我會這樣做。基本上,代碼只是await繼續(爲清楚起見沒有異常處理)面前的任務的前掛起的實例:

// the current pending task (initially a completed stub) 
Task _pendingTask = Task.FromResult<bool>(true); 

async void button_Click(object sender, RoutedEventArgs e) 
{ 
    var previousTask = _pendingTask; 

    _pendingTask = Task.Run(async() => 
    { 
     await previousTask; 

     Console.WriteLine("start"); 
     Thread.Sleep(5000); 
     Console.WriteLine("end"); 
    }); 

    // the following "await" is optional, 
    // you only need it if you have other things to do 
    // inside "button_Click" when "_pendingTask" is completed 
    await _pendingTask; 
} 

[更新]爲了解決這個評論,這裏有一個線程安全的版本,當button_Click可以可以同時調用:

Task _pendingTask = Task.FromResult<bool>(true); 
object _pendingTaskLock = new Object(); 

async void button_Click(object sender, RoutedEventArgs e) 
{ 
    Task thisTask; 

    lock (_pendingTaskLock) 
    { 
     var previousTask = _pendingTask; 

     // note the "Task.Run" lambda doesn't stay in the lock 
     thisTask = Task.Run(async() => 
     { 
      await previousTask; 

      Console.WriteLine("start"); 
      Thread.Sleep(5000); 
      Console.WriteLine("end"); 
     }); 

     _pendingTask = thisTask; 
    } 

    await thisTask; 
} 
+0

可能有多個「待處理」任務。 – JeffRSon

+1

@JeffRSon,只要它們都以'button_Click'(或另一個包裝器方法)開頭,它就可以工作多個。這是我測試[初始版本](http://stackoverflow.com/revisions/21178958/1)的方法:'for(var i = 0; i <10; i ++)button_Click(null,null);'它的工作原理是將'_pendingTask'成員捕獲到本地'previousTask'中,以便在'Task.Run' lambda中使用。 – Noseratio

+0

啊 - 好吧。所以你會依賴GUI線程作爲「同步」的手段。 – JeffRSon

2

什麼用(默認)嘗試Dataflow.ActionBlock<T>最大程度的並行1.這種方式,你不必擔心任何的線程安全/鎖定的擔憂。

它可能看起來像:

... 
var _block = new ActionBlock<bool>(async b => 
       { 
        Console.WriteLine("start"); 
        await Task.Delay(5000); 
        Console.WriteLine("end"); 
       }); 
... 


async void button_Click(object sender, RoutedEventArgs e) 
{ 
    await _block.SendAsync(true); 
} 

你也設置了ActionBlock接收TaskFunc<Task>,和簡單的運行/等待此輸入​​。這將允許多個操作排隊並等待來自不同的來源。