我和一个同事讨论这个,我们都不确定。鉴于这两种方法使用 C# 中的异步框架,两者之间的性能会有所不同吗?
public async Task<ReadRespone> MethodA(ReadRequest request)
{
var response = SynchronousReadThatTakesAWhileToExecute();
return await Task.FromResult(new ReadResponse(response));
}
public async Task<ReadResponse> MethodB(ReadRequest request)
{
var response = await Task.FromResult(SynchronousReadThatTakesAWhileToExecute());
return new ReadResponse(response);
}
我在想 MethodA 会同步执行慢速方法,然后,因为它只使用 await 作为一个普通的构造函数,实际上是一个同步方法,它会阻止其他线程的执行。我的同事认为两者在实践中是一样的,异步方法应该总是返回一个任务(但我认为 await 已经“拆箱”了任务,并且异步方法只需要在某处有一个等待任务在里面是正确的)。
谢谢
你的同事是对的。
MethodA
和 MethodB
实际上是等价的。它们都是具有同步实现的异步外观。每次调用这些方法时,调用线程完成所有工作,然后交还一个已经完成的任务 (.IsCompleted == true
)。任务包含结果或异常。 async
关键字仅有助于简化结果/异常包装。您可以在没有 async
的情况下实现完全相同的行为,但需要多一点代码:
public Task<ReadResponse> MethodC(ReadRequest request)
{
try
{
var response = SynchronousReadThatTakesAWhileToExecute();
return Task.FromResult(new ReadResponse(response));
}
catch (OperationCanceledException oce)
{
return Task.FromCanceled<ReadResponse>(oce.CancellationToken);
}
catch (Exception ex)
{
return Task.FromException<ReadResponse>(ex);
}
}
非
async
方法MethodC
用于教育/演示目的(它有一个隐藏的包)。在实践中,最好使用 async
进行包装。
正如其他人指出的那样,
Task.FromResult()
并不是那样工作的。它接受一个值并返回一个已完成的任务和该值。你想使用 Task.Run
或 TaskFactory.StartNew
.
public async Task<ReadResponse> Foo(ReadRequest request)
{
var result = await Task.Run(SynchronousReadThatTakesAWhileToExecute);
return new ReadResponse(result);
}
注意
SynchronousReadThatTakesAWhileToExecute
后没有括号。这是因为我没有直接调用该函数,而是将其传递给 Task.Run
。这在功能上等同于将 () => SynchronousReadThatTakesAWhileToExecute()
作为参数传递给 Task.Run
。
编辑我的回复,使其真正回答问题: 提供的两个选项在性能方面几乎相同。
基本相同。两者都将执行阻塞工作而不将控制权交还给调用方法。 异步状态机如果任务已经完成,编译器生成的将“快捷方式”,实际上将继续执行当前方法。这可以用以下代码演示:
async Task FakeAsync()
{
Console.WriteLine("Before IN FakeAsync");
await Task.CompletedTask;
Thread.Sleep(500); // "simulate" CPU-bound work
Console.WriteLine("After IN FakeAsync");
}
async Task RealAsync()
{
Console.WriteLine("Before IN RealAsync");
await Task.Yield();
Thread.Sleep(500); // "simulate" CPU-bound work
Console.WriteLine("After IN RealAsync");
}
Console.WriteLine("Before FakeAsync");
var task = FakeAsync();
Console.WriteLine("After FakeAsync");
await task;
Console.WriteLine("After await FakeAsync");
Console.WriteLine("---------");
Console.WriteLine("Before RealAsync");
var task1 = RealAsync();
Console.WriteLine("After RealAsync");
await task1;
Console.WriteLine("After await RealAsync");
这将给出以下输出:
Before FakeAsync
Before IN FakeAsync
After IN FakeAsync
After FakeAsync
After await FakeAsync
---------
Before RealAsync
Before IN RealAsync
After RealAsync
After IN RealAsync
After await RealAsync
请注意,对于
FakeAsync
,两个“IN”语句都在调用方法的其余部分之前执行,而对于RealAsync
,控制权返回给调用方法(因此“IN”之间的“After RealAsync” s)
Task.Yield
技巧在不存在同步上下文时会起作用,因此它可以用于某些测试场景,对于“现实生活”,更好的方法是Task.Run
:
async Task RealAsync()
{
Console.WriteLine("Before IN RealAsync");
await Task.Run(() => Thread.Sleep(500)); // "simulate" CPU-bound work)
Console.WriteLine("After IN RealAsync");
}
阅读更多:
正如其他人所指出的,这些方法之间几乎没有区别。
然而,这两种方法都是完全同步的,即使它们具有异步签名。我不推荐这样做,因为使用这段代码的任何人都会感到困惑。
如果你可以让它异步,那么就这样做:
public async Task<ReadRespone> MethodAsync(ReadRequest request)
{
var response = await AsynchronousReadThatTakesAWhileToExecuteAsync();
return new ReadResponse(response);
}
如果工作只是同步的(你不能让它异步),那么让你的方法同步:
public ReadRespone Method(ReadRequest request)
{
var response = SynchronousReadThatTakesAWhileToExecute();
return new ReadResponse(response);
}
如果你有同步工作(你不能让它异步),并且你的方法必须是异步的(即它正在实现一个接口方法),那么你可以同步实现一个异步接口。请注意,此代码同步运行,这让消费者感到惊讶,因此我建议在接口文档本身中记录可能同步的性质。
#pragma warning disable 1998
public async Task<ReadRespone> MethodAsync(ReadRequest request)
#pragma warning restore 1998
{
var response = SynchronousReadThatTakesAWhileToExecute();
return new ReadResponse(response);
}