假定需要按顺序处理一系统工作,它们可能消耗较多时间,GUI线程期望获取每件工作的结果并予显示,同时处理掉异常而不是中断整体工作,比如使用同一个Http接口以上传文件,并发访问将会报错。
常规做法是使用一个后台线程,线程遍历工作集合,并把结果POST到主线程,逻辑如下:
AsyncOperation asyncOperation = null;
private void button1_Click(object sender, EventArgs e)
{
asyncOperation = AsyncOperationManager.CreateOperation(null);
Thread thread = new Thread(loopWork);
thread.IsBackground = true;
List<int> arr = Enumerable.Range(0, 10).ToList();
thread.Start(arr);
}
private void loopWork(object obj)
{
List<int> arr = obj as List<int>;
foreach (var item in arr)
{
try
{
string result = doWork(item); //do some job
asyncOperation.Post(showResult, result); //show result
}
catch (Exception ex)
{
asyncOperation.Post(showError, ex.ToString()); //handle error
}
}
}
doWork、showResult、showError等方法不再列出。这些方法都是接收object类型参数的void类型(即Action<object>)委托的实例,示例使用AsyncOperation.Post()方法实质上使用了Form窗体的同步上下文,更多内容请查阅CSDN。
Task引入后任务调度器TaskScheduler的使用,同样是对同步上下文的使用,结果返回与异常处理不再分散到类的各种方法,避免了各种装箱拆箱。此例需求复杂了点,实现如下:
private void button2_Click(object sender, EventArgs e)
{
var task = new Task(() => { });
ConcurrentQueue<int> queue = new ConcurrentQueue<int>(Enumerable.Range(0, 10));
while (!queue.IsEmpty)
{
int item;
if (queue.TryDequeue(out item))
{
task.ContinueWith(t => doWork(item), TaskContinuationOptions.ExecuteSynchronously)
.ContinueWith(t =>
{
if (t.Exception == null)
{
Console.WriteLine(t.Result);
}
else
{
MessageBox.Show(t.Exception.GetBaseException().ToString());
}
}, TaskScheduler.FromCurrentSynchronizationContext());
}
else
{
Console.WriteLine("TryDequeue failed");
}
}
task.Start();
}
ConcurrentQueue<T>是先进先出的线程安全的集合,使用非常简单,相关概念仍请查阅CSDN。TaskContinuationOptions.ExecuteSynchronously保证了任务的排队顺序。
逻辑是先创建一个什么也不干的任务,然后遍历集合,为该任务顺序添加工作,并在工作完成后处理结果与异常。
示例已经过测试。