我在任务取消模式上发现了一个问题,我想了解为什么应该以这种方式工作。
考虑这个小程序,其中辅助线程执行异步“长”任务。同时,主线程通知取消。
该程序是一个较大的程序的非常简化的版本,可以有许多并发线程来执行“长任务”。当用户要求取消时,应取消所有正在运行的任务,因此取消了CancellationTokenSource集合。
class Program { static MyClass c = new MyClass(); static void Main(string[] args) { Console.WriteLine("program=" + Thread.CurrentThread.ManagedThreadId); var t = new Thread(Worker); t.Start(); Thread.Sleep(500); c.Abort(); Console.WriteLine("Press any key..."); Console.ReadKey(); } static void Worker() { Console.WriteLine("begin worker=" + Thread.CurrentThread.ManagedThreadId); try { bool result = c.Invoker().Result; Console.WriteLine("end worker=" + result); } catch (AggregateException) { Console.WriteLine("canceled=" + Thread.CurrentThread.ManagedThreadId); } } class MyClass { private Listcollection = new List (); public async Task Invoker() { Console.WriteLine("begin invoker=" + Thread.CurrentThread.ManagedThreadId); var cts = new CancellationTokenSource(); c.collection.Add(cts); try { bool result = await c.MyTask(cts.Token); return result; } finally { lock (c.collection) { Console.WriteLine("removing=" + Thread.CurrentThread.ManagedThreadId); c.collection.RemoveAt(0); } Console.WriteLine("end invoker"); } } private async Task MyTask(CancellationToken token) { Console.WriteLine("begin task=" + Thread.CurrentThread.ManagedThreadId); await Task.Delay(2000, token); Console.WriteLine("end task"); return true; } public void Abort() { lock (this.collection) { Console.WriteLine("canceling=" + Thread.CurrentThread.ManagedThreadId); foreach (var cts in collection) //exception here! { cts.Cancel(); } //collection[0].Cancel(); }; } } }
尽管锁定了收集访问,但是访问它的线程与请求取消的线程相同。也就是说,在迭代过程中修改了集合,并引发了异常。
为了更好的说明,您可以注释掉整个“ foreach”,并取消注释最后一条指令,如下所示:
public void Abort() { lock (this.collection) { Console.WriteLine("canceling=" + Thread.CurrentThread.ManagedThreadId); //foreach (var cts in collection) //exception here! //{ // cts.Cancel(); //} collection[0].Cancel(); }; }
这样做也不例外,程序会正常终止。但是,有趣的是所涉及线程的ID:
program=10 begin worker=11 begin invoker=11 begin task=11 canceling=10 removing=10 end invoker Press any key... canceled=11
显然,“最终”主体在调用者线程上运行,但是一旦离开“调用者”,该线程将成为辅助线程。
为什么在辅助线程中不执行“ finally”块?