利用 Task 建立長時工作並可以取消的簡單範例
常見到一個問題『如果建立了一個長時的工作又要能取消,該怎麼做?』,我的建議是『若 Framework 的版本允許,使用 Task 來解決這問題是比較適當的做法』。
假設 Windows Forms 畫面上有兩個 Button 和一個 Label,button1 按下時會執行一個長時的程序,範例中是一直改變 Label.Text 屬性 ,按下button2 則取消這個程序,那該怎麼做?
首先,先為取消工作建立一個 CancellationTokenSource 型別的欄位,因為取消工作需要 CancellationToken。
private CancellationTokenSource cts;
建立 button1.Click 事件的委派方法:
async private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
cts = new CancellationTokenSource();
int i = 0;
try
{
await Task.Run (() =>
{
while (true)
{
this.Invoke(new Action(() => label1.Text = i.ToString()));
i++;
if (cts.Token.IsCancellationRequested)
{
cts.Token.ThrowIfCancellationRequested();
}
}
}, cts.Token
);
}
catch (OperationCanceledException ex)
{
Debug.WriteLine("Task Canceled");
}
finally
{
button1.Enabled = true;
}
}
在 button1.Click 事件的委派方法內先建立 CancellationTokenSource 的執行個體,使用 Task.Run(Func<Task> function, CancellationToken cancellationToken) 方法來執行長時工作,其中:
() =>
{
while (true)
{
this.Invoke(new Action(() => label1.Text = i.ToString()));
i++;
if (cts.Token.IsCancellationRequested)
{
cts.Token.ThrowIfCancellationRequested();
}
}
}
這一段 Lambda 就是要傳入給 Func<Task> function 的引數,而 cts.Token 則是要傳遞給 CancellationToken cancellationToken 的引數。在迴圈的最後加上的
if (cts.Token.IsCancellationRequested)
{
cts.Token.ThrowIfCancellationRequested();
}
則是用於檢查工作是否已被取消,如果收到取消訊號則發出 OperationCanceledException (這就是為什麼需要 try catch 的原因)。
至於 button2 的處理就簡單多了,發出取消訊號就好:
private void button2_Click(object sender, EventArgs e)
{
if (cts != null && !cts.IsCancellationRequested)
{
cts.Cancel();
}
}
很簡單的實作就可以解決類似的問題。