Я запускаю 2 задачи, без await
-инга их, и одна из них зависит от другой.
Я пытаюсь понять, почему следующий фрагмент кода блокирует.
public class Tasks {
EventWaitHandle handle = new EventWaitHandle(false, EventResetMode.ManualReset);
public async Task Job1() {
Console.WriteLine("Finished job1");
handle.Set();
}
public async Task Job2() {
handle.WaitOne();
Console.WriteLine("Doing Job2 work");
}
}
class Program {
static async Task Main(string[] args) {
Tasks seq = new Tasks();
var t2 =seq.Job2();
var t1 =seq.Job1();
await Task.WhenAll(t1, t2);
Console.WriteLine("finished both");
}
}
Если я создаю CPU-bound
задачи для обеих моих задач, это работает:
var t2=Task.Run(seq.Job2);
var t1=Task.Run(seq.Job1);
Я также попытался поместить обе задачи в отдельную задачу от основного потока, и она все еще блокируется:
var bigtask=Task.Run(async()=>{
var t2 =seq.Job2();
var t1 =seq.Job1();
});
Если я запускаю задачу без ожидания, разве это не почти то же самое, что запускать новую CPU-bound
задачу? (Task.Run
)
Посмотрите на предупреждения вашего компилятора; они скажут вам точно, что происходит не так. В частности, вы используете async
без await
, поэтому эти методы будут запускать синхронно.
Task.Run
выполняет метод в потоке пула потоков, что предотвращает его синхронное выполнение.
If i launch a task without await-ing it isn't it almost the same as [using Task.Run]?
Каждый метод async
начинает выполняться синхронно; await
— это точка, в которой он может вести себя асинхронно.
async
сам по себе не использует потоки (или пул потоков); это больше похоже на более причудливый синтаксис для обратных вызовов. Task.Run
использует пул потоков.
Чтобы решить вашу основную проблему (когда одна задача ожидает другую), самый простой подход — передать Task
, возвращенный из Job1
, в метод Job2
и получить Job2
await
эту задачу. Если это невозможно, вам нужен асинхронный сигнал (а не блокирующий, как EventWaitHandle
). Одноразовый асинхронный сигнал TaskCompletionSource<T>
; SemaphoreSlim
также поддерживает асинхронное ожидание; и более сложные примитивы координации являются частью моего Библиотека AsyncEx.
Объявление методов «асинхронными» не делает ваш код автоматически многопоточным. Вы можете предположить, что ваш код будет работать синхронно, пока что-то не будет "ожидаться".
Проблема здесь в том, что Job2 никогда не вернется, поэтому ваш код застрянет. Но, например (а не фактическое решение), если вы сделали что-то вроде этого:
public async Task Job2()
{
await Task.Delay(1000);
handle.WaitOne();
Console.WriteLine("Doing Job2 work");
}
Ваша программа на самом деле завершится, потому что функция станет асинхронной и вернется к вызывающей стороне, как только ожидается задержка.
Использование примитивов синхронизации, таких как «EventWaitHandle/ManualResetEvent», обычно следует избегать в TPL (async/await), поскольку они физически блокируют поток, а не освобождают его и ожидают обратного вызова.
Вот реальное решение:
public class Tasks
{
SemaphoreSlim semaphore = new SemaphoreSlim(0);
public async Task Job1()
{
Console.WriteLine("Finished job1");
semaphore.Release();
}
public async Task Job2()
{
await semaphore.WaitAsync();
Console.WriteLine("Doing Job2 work");
}
}
class Program
{
static async Task Main(string[] args)
{
Tasks seq = new Tasks();
var t2 = seq.Job2();
var t1 = seq.Job1();
await Task.WhenAll(t1, t2);
Console.WriteLine("finished both");
}
}
Ваши асинхронные методы Job1 и Job2 не включают операции ввода-вывода, не включают ожидание или фактически создают и возвращают задачи. Поэтому, когда вы их вызываете, вы запускаете их последовательно в «основном» потоке. И ваш код естественно блокирует.