多次等待同一任务可能会导致阻塞 [英] Multiple await on same task may cause blocking

查看:163
本文介绍了多次等待同一任务可能会导致阻塞的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在同一Task上使用多个等待时应格外小心. 我在尝试使用BlockingCollection.GetConsumingEnumerable()方法时遇到了这种情况. 最终完成了这个简化的测试.

It should be careful to use several awaits on same Task. I have encountered with such situation while trying to use BlockingCollection.GetConsumingEnumerable() method. And ends up with this simplified test.

class TestTwoAwaiters
{
    public void Test()
    {
        var t = Task.Delay(1000).ContinueWith(_ => Utils.WriteLine("task complete"));
        var w1 = FirstAwaiter(t);
        var w2 = SecondAwaiter(t);

        Task.WaitAll(w1, w2);
    }

    private async Task FirstAwaiter(Task t)
    {
        await t;
        //await t.ContinueWith(_ => { });
        Utils.WriteLine("first wait complete");
        Task.Delay(3000).Wait(); // execute blocking operation
    }

    private async Task SecondAwaiter(Task t)
    {
        await t;
        Utils.WriteLine("second wait complete");
        Task.Delay(3000).Wait(); // execute blocking operation
    }

}

我认为这里的问题是任务的延续将相应地在一个线程上执行订阅者. 而且,如果一个侍者执行阻止操作(例如BlockingCollection.GetConsumingEnumerable()的屈服),它将阻止其他侍者并且他们无法继续工作. 我认为可能的解决方案是在等待任务之前调用ContinueWith(). 它将分成两个部分,并且阻塞操作将在新线程上执行.

I think the problem here is the continuation of a task will execute subscribers on a one thread consequentially. And if one awaiter execute a blocking operation (such a yielding from BlockingCollection.GetConsumingEnumerable()) it will block other awaiters and they couldn't continue their work. I think a possible solution will be to call ContinueWith() before await a task. It will break a continuation to two parts and blocking operation will be executed on a new thread.

某人可以多次确认或否认等待一项任务的可能性. 如果很常见,那么绕过阻塞的正确方法是什么?

Can someone confirm or disprove a possibility to await on a task several times. And if it is common then what is a proper way to get around blocking?

推荐答案

请考虑以下代码:

private static async Task Test() {
        Console.WriteLine("1: {0}, thread pool: {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
        await Task.Delay(1000);
        Console.WriteLine("2: {0}, thread pool: {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
        await Task.Delay(1000);
        Console.WriteLine("3: {0}, thread pool: {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
        await Task.Delay(1000);
        Console.WriteLine("4: {0}, thread pool: {1}", Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);
    }

如果运行它,您将看到以下输出:

If you run it, you will see the following output:

1: 9, thread pool: False
2: 6, thread pool: True
3: 6, thread pool: True
4: 6, thread pool: True

您在这里看到,如果没有SynchonizationContext(或者您不使用ConfigureAwait),并且在等待完成后已经在线程池线程上运行,它将更改线程以继续.这正是您的代码中发生的情况:在FirstAwaiter和SecondAwaiter中完成"await t"语句后,在两种情况下,连续操作都在同一线程上运行 ,因为这是运行Delay(1000)的线程池线程.当然,在FirstAwaiter执行它的延续时,SecondAwaiter将阻塞,因为它的延续被发布到同一线程池线程中.

You see here that if there is no SynchonizationContext (or you don't use ConfigureAwait) and after await completes it's already running on thread pool thread, it will not change thread for continuation. This is exactly what happens in your code: after "await t" statement completes in FirstAwaiter and SecondAwaiter, continuation runs on the same thread in both cases, because it's thread pool thread where Delay(1000) ran. And of course while FirstAwaiter performs it's continuation, SecondAwaiter will block since it's continuation is posted to the same thread pool thread.

如果您将使用ContinueWith而不是await,则可以修复"您的问题(但仍请注意问题的注释):

if you will use ContinueWith instead of await, you can kind of "fix" your problem (but note the comments to your question still):

internal class TestTwoAwaiters {
    public void Test() {
        Console.WriteLine("Mail thread is {0}", Thread.CurrentThread.ManagedThreadId);
        var t = Task.Delay(1000).ContinueWith(_ => {
            Console.WriteLine("task complete on {0}", Thread.CurrentThread.ManagedThreadId);
        });
        var w1 = FirstAwaiter(t);
        var w2 = SecondAwaiter(t);
        Task.WaitAll(w1, w2);
    }

    private static Task FirstAwaiter(Task t) {
        Console.WriteLine("First await on {0}", Thread.CurrentThread.ManagedThreadId);
        return t.ContinueWith(_ =>
        {
            Console.WriteLine("first wait complete on {0}", Thread.CurrentThread.ManagedThreadId);
            Task.Delay(3000).Wait();
        });
    }

    private static Task SecondAwaiter(Task t) {
        Console.WriteLine("Second await on {0}", Thread.CurrentThread.ManagedThreadId);
        return t.ContinueWith(_ => {
            Console.WriteLine("Second wait complete on {0}", Thread.CurrentThread.ManagedThreadId);
            Task.Delay(3000).Wait();
        });
    }
}

这篇关于多次等待同一任务可能会导致阻塞的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆