Я нашел проблему с шаблоном аннулирования задачи, и я хотел бы понять, почему это должно работать таким образом.Почему отмена задачи происходит в потоке вызывающего абонента?
Рассмотрите эту небольшую программу, где вторичный поток выполняет асинхронную «длинную» задачу. В то же время основной поток уведомляет об аннулировании.
Программа является очень упрощенной версией более крупной, которая может иметь много параллельных потоков, выполняющих «длинную задачу». Когда пользователь попросит отменить, вся работающая задача должна быть отменена, следовательно, коллекция 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 List<CancellationTokenSource> collection = new List<CancellationTokenSource>();
public async Task<bool> 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<bool> 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();
};
}
}
}
Несмотря на блокировку доступа к коллекции, доступ к ней осуществляется так же, как и запрос на отмену. Это значит, что коллекция изменяется во время итерации, и возникает исключение.
Для большей ясности, вы можете закомментировать весь «Еогеасп» и раскомментируйте самую последнюю команду, следующим образом:
public void Abort()
{
lock (this.collection)
{
Console.WriteLine("canceling=" + Thread.CurrentThread.ManagedThreadId);
//foreach (var cts in collection) //exception here!
//{
// cts.Cancel();
//}
collection[0].Cancel();
};
}
Это, нет исключений, и программа завершается корректно. Тем не менее, это интересно узнать идентификатор резьбы участвующей:
program=10
begin worker=11
begin invoker=11
begin task=11
canceling=10
removing=10
end invoker
Press any key...
canceled=11
Судя по всему, «наконец» телу работать на потоке вызывающего абонента, но когда-то от «заклинателя», поток является вторичным.
Почему блок «finally» вместо этого не выполняется во вторичном потоке?
Также интересно отметить, что аннулирование передается с 'await Task.Delay (3000, токен);': catch и rethrow 'Exception' в MyTask, а также catch и rethrow' Exception' в Invoker - и вы увидите что это 'TaskCanceledException' в MyTask (поток, который мы назвали Cancel() from),' TaskCanceledException' в Invoker (также поток, который мы назвали Cancel()), и, наконец, он становится 'AggregateException', но в потоке, который ждал Результат –
'TaskScheduler.FromCurrentSynchronizationContext(). Это никогда не будет работать в консольном режиме, так как оно не имеет его. «Не« никогда ». Вы можете вручную создать контекст синхронизации и установить его в текущем в консольном приложении. «Обычно» может быть уместно здесь, а не «никогда». – Servy