[C#.NET][TPL] Use async / await Get Task Process Report
在上面幾篇文章裡,我都是使用 SynchronizationContext 來處理控制項的跨執行緒更新,如下列程式碼:
public Form1()
{
InitializeComponent();
m_SynchronizationContext = SynchronizationContext.Current;
}
var mainTask = new Task<int>(() =>
{
int result = 0;
for (int i = 0; i < 100; i++)
{
result++;
SpinWait.SpinUntil(() =>
{
m_SynchronizationContext.Post(_ => { this.label1.Text = result.ToString(); }, null);
return false;
}, 10);
}
return result;
});
當然你也可以像以下這樣用
@Winform 專案,用 Invoke/BeginInvoke:
this.Invoke((Action)(() => { this.label1.Text = "Demo"; }));
@WPF 專案,用 Dispatcher.Invoke:
畫面跟邏輯包在一起寫雖然很方便,一旦專案越來越大,維護起來就會越來越痛苦,這該怎麼辦呢?續上篇的例子 [TPL] 初探 async 和 await
新增一個 ProgressInfo 類別:
internal class ProgressInfo
{
public long Data { get; internal set; }
}
然後替 SumAsync 方法加了兩個參數:
修正方法:
- 在方法體加上 async 關鍵字,必須要回傳 Task<long>,而不是 long
- 調用 CancellationToken.ThrowIfCancellationRequested 方法(選項),取消機制,這不是本篇的重點,若需要可參考 [TPL] 任務取消通知
- 調用 IProgress.Report 方法,用來通知處理過程。
private async Task<long> SumAsync(CancellationToken cancellationToken, IProgress<ProgressInfo> progress)
{
long sum = 0;
var info = new ProgressInfo();
for (int i = 0; i < 1000; i++)
{
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
sum++;
if (progress != null)
{
info.Data = sum;
progress.Report(info);
}
SpinWait.SpinUntil(() =>
{
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
return false;
}, 10);
}
return sum;
}
調用方式:
- 在方法體加上 async 關鍵。
- 註冊 Progress.ProgressChanged 事件,接收 SumAsync 的回報。
- 使用 Task.Factory.StartNew 調用 SumAsync,並加入 await,會回傳 Task<long>,藉此可以處理更多任務屬性。
若將 Task.Factory.StartNew 換成 Task.Run,會回傳 long,直接得到答案,有興趣的可以試試看。
private async void button_AsyncStart_Click(object sender, EventArgs e)
{
if (this.m_cts.IsCancellationRequested)
{
return;
}
label1.Text = "";
label2.Text = "";
var progress = new Progress<ProgressInfo>();
progress.ProgressChanged += (o, info) =>
{
label1.Text = info.Data.ToString();
};
var maskTask = await Task.Factory.StartNew(() => SumAsync(this.m_cts.Token, progress)); var status = string.Format("任務完成,完成狀態為:\r\nIsCanceled={0}\r\nIsCompleted={1}\r\nIsFaulted={2}\r\n",
maskTask.IsCanceled,
maskTask.IsCompleted,
maskTask.IsFaulted);
if (!maskTask.IsCanceled && !maskTask.IsFaulted)
{
status += string.Format("\r\n計算結果:{0}", maskTask.Result);
}
label2.Text = status.ToString();
}
執行結果如下:
接下來換個寫法
修正方法:
- 再修改一下 SumAsync 方法體,把 aysnc 拿掉
- 在裡面建立一條 Task,並回傳 Task
private Task<long> SumAsync1(CancellationToken cancellationToken, IProgress<ProgressInfo> progress)
{
var task = Task.Factory.StartNew(() =>
{
long sum = 0;
var info = new ProgressInfo();
for (int i = 0; i < 100; i++)
{
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
sum++;
if (progress != null)
{
info.Data = sum;
progress.Report(info);
}
SpinWait.SpinUntil(() =>
{
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
return false;
}, 10);
}
return sum;
});
return task;
}
調用方式:
- 直接下達 await 調用 SumAsync 方法,會回傳 long 結果,而不是 Task<long>
private async void AsyncStart1_Button_Click(object sender, EventArgs e)
{
label1.Text = "";
label2.Text = "";
var progress = new Progress<ProgressInfo>();
progress.ProgressChanged += (o, info) =>
{
label1.Text = info.Data.ToString();
};
var result = await SumAsync1(this.m_cts.Token, progress);
label2.Text = string.Format("計算結果:{0}", result.ToString());
}
執行結果如下:
結論:
提供了兩種寫法處理 SumAsunc,請依自己的需求選用。
以上的寫法都是利用 async、await 關鍵字來做處理,不用再處理 SynchronizationContext,控制項跨執行緒更新,程式碼變得更好懂。
專案位置:https://github.com/yaochangyu/sample.dotblog/tree/master/TPL/Winform/Progress.Report%20Update%20UI
文章出自:http://www.dotblogs.com.tw/yc421206/archive/2013/06/14/105514.aspx
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET