为什么未等待的任务被阻止

时间:2019-04-10 07:00:31

标签: task-parallel-library blocking event-wait-handle

我正在启动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

2 个答案:

答案 0 :(得分:2)

查看您的编译器警告;他们会确切告诉您出了什么问题。具体来说,您使用的是async,而没有await,因此这些方法将同步运行

Task.Run在线程池线程上执行该方法,从而阻止其同步运行。

  

如果我没有等待就启动任务,这与[使用Task.Run]几乎一样吗?

Every async method starts executing synchronously; await是它可以异步运行的地方。

async本身不使用任何线程(或线程池);它更像是回调的更高级语法。 Task.Run确实使用了线程池。


要解决您的基本问题(让一个任务等待另一任务),最简单的方法是将Task返回的Job1传递给Job2方法,并让{{1 }} Job2的任务。如果那不可能,那么您需要一种异步信号(而不是像await这样的阻塞信号)。一次性异步信号为EventWaitHandleTaskCompletionSource<T>还支持异步等待;更复杂的协调原语是我的AsyncEx library的一部分。

答案 1 :(得分:1)

将方法声明为“异步”并不会自动使您的代码成为多线程。您可以假设您的代码将同步运行,直到“等待”。

这里的问题是Job2将永远不会返回,因此您的代码将被卡住。 但是,例如(不是实际的解决方案),如果您执行了以下操作:

            public async Task Job2()
            {
                await Task.Delay(1000);
                handle.WaitOne();
                Console.WriteLine("Doing Job2 work");
            }

您的程序实际上将退出,因为该功能将变为异步状态,并在等待延迟后返回给调用者。

通常应在TPL(async / await)中避免使用“ EventWaitHandle / ManualResetEvent”之类的同步原语,因为它们会物理上阻塞线程,而不是释放线程并等待回调。

这是一个实际的解决方案:

    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");
        }
    }