C#异步/等待等待不等待

时间:2019-10-17 11:50:09

标签: c# async-await

我正在使用.Net 4.7.2和C#7

在一个类中,有一些服务器方法,全部无效。其中一些可以异步执行,其他一些则必须顺序执行。因此,我为并联定义了一个列表

private void ExecuteSequential()
{
    SomeMethod1();
    SomeMethod2();
    ExecuteParallelAsync();
    SomeMethod3();
    SomeMethod4();
}


private async void ExecuteParallelAsync()
{
    List<Action> methods = new List<Action>()
    {
        MyMethod1,
        MyMethod2,
        MyMethod3
    };


    await Task.Run( () => { Parallel.ForEach( methods , ( currentMethod ) => currentMethod.Invoke() ); } );            
}

SomeMethod3 恰好被执行之前, ExecuteParallelAsync 应该完全完成。 因为不是这种情况,所以我对async和await的使用显然有问题。

我做错了什么?

谢谢!

5 个答案:

答案 0 :(得分:11)

async void-所有投注均关闭;实际上,除非您处在需要它们的极端极端情况的微小组中,否则您应该从不编写async void方法,例如异步事件-处理程序。

  

在执行SomeMethod3之前,ExecuteParallelAsync应该完全完成。

它怎么可能知道? async void,这意味着它不给呼叫者任何指示状态的信息

这样做,必须 为:

private async Task ExecuteParallelAsync() // or ValueTask

必须在呼叫站点等候

await ExecuteParallelAsync();

答案 1 :(得分:3)

马克的答案是正确的解决方案,但我想解释更多有关原因的解释。

了解async方法的一个非常关键的事情是:

  1. 它们同步启动。
  2. 使用await关键字,并且await给出不完整的Task时,该方法返回-是的,即使您只是在中间您的方法。魔术在返回什么中。

因此,让我们看一下代码的作用:

  1. 致电ExecuteParallelAsync()
  2. ExecuteParallelAsync()开始同步运行。
  3. Task.Run返回一个尚未完成的Task对象。
  4. await关键字检查Task,发现它不完整,因此它创建了一个新的Task,并将该方法的其余部分作为该{{ 1}}(在这种情况下,无需执行任何操作,但仍然会发生)并返回
  5. 由于返回类型为Task,因此无法返回void。但它仍然返回!

因此,这意味着TaskExecuteParallelAsync()完成之前就返回了,Task.Run无法告知工作何时真正完成。

这通常称为“放火忘了”(即“去做,我不在乎何时或是否完成”)。

正如马克的答案所指出的,如果您想知道何时完成,则需要返回一个ExecuteSequential(),然后您可以Task来知道它何时完成。

Microsoft有关于异步编程的写得很好的文章,您可以从中受益:Asynchronous programming with async and await

答案 2 :(得分:1)

如果您想要不使用async / await sugar关键字的实现:

private void ExecuteParallelAsync()
{
    List<Action> methods = new List<Action>()
    {
        MyMethod1,
        MyMethod2,
        MyMethod3
    };

    Task.WaitAll(methods.Select((action) => Task.Run(action)).ToArray())   
}

答案 3 :(得分:0)

步骤1,复制

public void Method()
    {
        SomeMethod();
        SomeMethod();
        ExecuteParallelAsync(); //< ---fire and forget 
        SomeMethod();
        SomeMethod();

        Console.ReadLine();
    }

    private void SomeMethod() => Console.WriteLine($"SomeMethod");

    private async void ExecuteParallelAsync()
    {
        Console.WriteLine($"START");
        await Task.Delay(TimeSpan.FromMilliseconds(100));
        Console.WriteLine($"END");
    }

输出

SomeMethod
SomeMethod
START
SomeMethod
SomeMethod
END

步骤2,尝试修复

“哦,是的,我忘记等待我的功能了”

await ExecuteParallelAsync(); //Change 1. "I forgot to await"

输出

Compilation error (line 12, col 3): The 'await' operator can only be used within an async method. Consider marking this method with the 'async' modifier and changing its return type to 'Task'.
Compilation error (line 12, col 3): Cannot await 'void'

第3步,修复

解决步骤2中的两个错误。

// Change 2
// was:public void Main()  
// which caused: "The 'await' operator can only (...)"
public async Task Main() 
(...)
// Change 3
// was: private async void ExecuteParallelAsync()
// which caused "Compilation error (line 12, col 3): Cannot await 'void'"
private async Task ExecuteParallelAsync()

运行。

输出:

SomeMethod
SomeMethod
START
END
SomeMethod
SomeMethod

作为参考,我们可以等待ExecuteParallelAsync而不使用await关键字,但是{<1}}是除 tiny 少数群体之外的所有情况下的正确方法不是。

答案 4 :(得分:0)

一个完整的小示例:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace test
{
    class Program
    {
        static void Main(string[] args)
        {
            SomeMethod1();
            ExecuteParallelAsync();
            SomeMethod2();
            Console.ReadKey();
        }

        static void SomeMethod1()
        {
            Console.WriteLine("SomeMethod1");
        }
        static void SomeMethod2()
        {
            Console.WriteLine("SomeMethod2");
        }

        static void ExecuteParallelAsync()
        {
            Console.WriteLine("\tstart of ExecuteParallelAsync");
            var rd = new Random();

            List<Action> methods = new List<Action>()
            {
                ()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod1"); },
                ()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod2"); },
                ()=>{Thread.Sleep((int) Math.Ceiling(rd.NextDouble()*1000));Console.WriteLine("MyMethod3"); },
            };
            Task.WaitAll(methods.Select((action) => Task.Run(action)).ToArray());

            Console.WriteLine("\tend of ExecuteParallelAsync");
        }

    }
}

结果示例:

SomeMethod1
        start of ExecuteParallelAsync
MyMethod2
MyMethod3
MyMethod1
        end of ExecuteParallelAsync
SomeMethod2