[C#.NET][TPL] 使用 async / await 處理任務進度回報

[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 方法加了兩個參數:

IProgress<T> 介面

CancellationToken 結構

 

修正方法:

  1. 在方法體加上 async 關鍵字,必須要回傳 Task<long>,而不是 long
  2. 調用 CancellationToken.ThrowIfCancellationRequested 方法(選項),取消機制,這不是本篇的重點,若需要可參考 [TPL] 任務取消通知
  3. 調用 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;
}


調用方式:

  1. 在方法體加上 async 關鍵。
  2. 註冊 Progress.ProgressChanged 事件,接收 SumAsync 的回報。
  3. 使用 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();
}

執行結果如下:

SNAGHTML530305b


接下來換個寫法

修正方法:

  • 再修改一下 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());

}

執行結果如下:

SNAGHTML530305b


結論:

提供了兩種寫法處理 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

Image result for microsoft+mvp+logo