簡體   English   中英

C# 異步/等待等待不等待

[英]C# async/await await doesn't await

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

在 class 中有幾種方法,都是無效的。 其中一些可以異步執行,另一些必須按順序執行。 所以我定義了一個平行列表

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 的使用有問題。

我做錯了什么?

提前致謝!

async void - 所有賭注都關閉; 實際上,您永遠不應該編寫async void方法,除非您處於需要它的極少數極端極端情況中,例如異步事件處理程序。

在 SomeMethod3 恰好被執行之前 ExecuteParallelAsync 應該完全完成。

它怎么可能知道? 它是: async void ,這意味着它不會向調用者提供任何指示 state 的內容

為此,它必須是:

private async Task ExecuteParallelAsync() // or ValueTask

必須在呼叫現場等待

await ExecuteParallelAsync();

Marc 的答案是正確的解決方案,但我想更多地解釋原因。

了解async方法的一個非常關鍵的事情是:

  1. 它們同步啟動。
  2. 當使用await關鍵字並且await給定一個不完整的Task時,該方法會返回- 是的,即使您的方法只完成了一半。 神奇在於它返回的東西

所以讓我們看看你的代碼做了什么:

  1. 調用ExecuteParallelAsync()
  2. ExecuteParallelAsync()開始同步運行。
  3. Task.Run返回一個尚未完成的Task object。
  4. await關鍵字檢查Task ,發現它不完整,所以它創建一個新Task ,注冊該方法的 rest 作為該Task的延續(在這種情況下沒有什么可做的,但它仍然會發生)並返回
  5. 因為返回類型是void它不能返回Task 但它仍然返回!

所以這一切意味着ExecuteParallelAsync()Task.Run完成之前返回,而ExecuteSequential()無法判斷工作何時真正完成。

這通常被稱為“一勞永逸”(即“去做這件事,我不在乎你何時或是否完成”)。

正如 Marc 的回答所指出的那樣,如果您想知道它何時完成,您需要返回一個Task ,然后您可以await知道它何時完成。

Microsoft 有非常好的關於異步編程的文章,您可能會從閱讀中受益: Asynchronous Programming with async and await

如果您想要一個沒有 async/await 糖關鍵字的實現:

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

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

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

Output

SomeMethod
SomeMethod
START
SomeMethod
SomeMethod
END

第2步,嘗試修復

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

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

Output

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'

第三步,修復

解決步驟 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()

跑。

Output:

SomeMethod
SomeMethod
START
END
SomeMethod
SomeMethod

作為參考,我們可以在沒有await關鍵字的情況下等待ExecuteParallelAsync ,但是await在所有場景中都是正確的方式,除了少數情況下它不是。

一個完整的小例子:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM