我正在使用.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的使用显然有问题。
我做错了什么?
谢谢!
答案 0 :(得分:11)
async void
-所有投注均关闭;实际上,除非您处在需要它们的极端极端情况的微小组中,否则您应该从不编写async void
方法,例如异步事件-处理程序。
在执行SomeMethod3之前,ExecuteParallelAsync应该完全完成。
它怎么可能知道? :async void
,这意味着它不给呼叫者任何指示状态的信息。
要这样做,必须 为:
private async Task ExecuteParallelAsync() // or ValueTask
您必须在呼叫站点等候
await ExecuteParallelAsync();
答案 1 :(得分:3)
马克的答案是正确的解决方案,但我想解释更多有关原因的解释。
了解async
方法的一个非常关键的事情是:
await
关键字,并且await
给出不完整的Task
时,该方法返回-是的,即使您只是在中间您的方法。魔术在返回什么中。因此,让我们看一下代码的作用:
ExecuteParallelAsync()
。ExecuteParallelAsync()
开始同步运行。Task.Run
返回一个尚未完成的Task
对象。await
关键字检查Task
,发现它不完整,因此它创建了一个新的Task
,并将该方法的其余部分作为该{{ 1}}(在这种情况下,无需执行任何操作,但仍然会发生)并返回。Task
,因此无法返回void
。但它仍然返回!因此,这意味着Task
在ExecuteParallelAsync()
完成之前就返回了,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)
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
“哦,是的,我忘记等待我的功能了”
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'
解决步骤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