C#链接ContinueWith不等待以前的任务完成

时间:2017-09-15 08:37:17

标签: c# asynchronous continuewith

我正在测试C#async / await的异步性,并且遇到一个惊喜,其中ContinueWith的后续代码不等待上一个任务完成:

allowAlphabetsOnly(event: KeyboardEvent): boolean {
    let charCode = (event.which) ? event.which : event.keyCode;
    if (charCode == 8) return true;
    let key;

    if (window.event) {
        key = event.keyCode;
    }
    else {
        if (event.which) {
            key = event.which;
        }
        else return true;
    }
    let keychar = String.fromCharCode(key);
    return /^[a-zA-Z\s]+$/.test(keychar);
}

使用以下测试方法执行:

public async Task<int> SampleAsyncMethodAsync(int number,string id)
  {
            Console.WriteLine($"Started work for {id}.{number}");
            ConcurrentBag<int> abc = new ConcurrentBag<int>();

            await Task.Run(() => { for (int count = 0; count < 30; count++) { Console.WriteLine($"[{id}] Run: {number}"); abc.Add(count); } });

            Console.WriteLine($"Completed work for {id}.{number}");
            return abc.Sum();
        }

输出显示运行执行0.0,1.0和2.0正确异步执行,但后续x.1和x.2几乎立即启动,x.2实际上在x.1之前完成。例如。如下所示:

        [Test]
        public void TestAsyncWaitForPreviousTask()
        {
            for (int count = 0; count < 3; count++)
            {
                int scopeCount = count;

                var c = SampleAsyncMethodAsync(0, scopeCount.ToString())
                .ContinueWith((prevTask) =>
                {
                    return SampleAsyncMethodAsync(1, scopeCount.ToString());
                })
                .ContinueWith((prevTask2) =>
                {
                    return SampleAsyncMethodAsync(2, scopeCount.ToString());
                });
            }
        }

似乎continueWith只会等待第一个任务(0)而不管后续的链。 我可以通过在第一个Continuewith块中嵌套第二个ContinueWith来解决问题。

我的代码有问题吗?我假设Console.WriteLine尊重FIFO。

1 个答案:

答案 0 :(得分:4)

简而言之,您希望ContinueWith等待以前返回的对象。在Task操作中返回一个对象(甚至是ContinueWith)对返回值没有任何作用,它不会等待它完成,它会返回它并传递给continuation(如果存在)。

以下事情确实发生了:

  • 您运行SampleAsyncMethodAsync(0, scopeCount.ToString())
  • 完成后,执行继续1:

    return SampleAsyncMethodAsync(1, scopeCount.ToString());
    

    当它偶然发现await Task.Run时,它会返回一个任务。即,它不等待SampleAsyncMethodAsync完成。

  • 然后,认为继续1已完成,因为它已返回值(任务)
  • 继续2运行。

如果您手动等待每个异步方法,那么它将运行:

for (int count = 0; count < 3; count++)
{
    int scopeCount = count;

    var c = SampleAsyncMethodAsync(0, scopeCount.ToString())
    .ContinueWith((prevTask) =>
    {
        SampleAsyncMethodAsync(1, scopeCount.ToString()).Wait();
    })
    .ContinueWith((prevTask2) =>
    {
        SampleAsyncMethodAsync(2, scopeCount.ToString()).Wait();
    });
}    

使用ContinueWith(async t => await SampleAsyncMethodAsync...也不起作用,因为它会导致包裹的Task<Task>结果(很好地解释here)。

此外,您可以执行以下操作:

for (int count = 0; count < 3; count++)
{
    int scopeCount = count;

    var c = SampleAsyncMethodAsync(0, scopeCount.ToString())
        .ContinueWith((prevTask) =>
        {
            SampleAsyncMethodAsync(1, scopeCount.ToString())
                .ContinueWith((prevTask2) =>
                {
                    SampleAsyncMethodAsync(2, scopeCount.ToString());
                });
        });   
}

然而,它会产生某种回调地狱并且看起来很乱。

您可以使用await使此代码更清晰:

for (int count = 0; count < 3; count++)
{
    int scopeCount = count;

    var d = Task.Run(async () => {
        await SampleAsyncMethodAsync(0, scopeCount.ToString());
        await SampleAsyncMethodAsync(1, scopeCount.ToString());
        await SampleAsyncMethodAsync(2, scopeCount.ToString());
    });
}   

现在,它为3个计数运行3个任务,因此每个任务将运行异步方法,number等于1,2和3。