在这种特定情况下,在异步方法中应该将`await`放在哪里。

huangapple go评论79阅读模式
英文:

Where to put await in an async method in this specific scenario

问题

I was dicussing this with a colleague, and none of us were sure about it. Given these two methods using the asynchronous framework in C#, will there be a difference in performance between the two?

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

I was thinking that MethodA would perform the slow method syncronously, and then, since it only uses await for a trivial constructor, would in practice be a synchronous method, which would block execution for other threads. My colleague argued that both would be the same in practice, and that an async method should always return a task (but I'm thinking that await already "unboxes" the Task, and that an asynch method only needs to have an await Task somewhere in it to be correct).

Thanks

英文:

I was dicussing this with a colleague, and none of us were sure about it. Given these two methods using the asynchronous framework in C#, will there be a difference in performance between the two?

public async Task&lt;ReadRespone&gt; MethodA(ReadRequest request)
{
    var response = SynchronousReadThatTakesAWhileToExecute();
    return await Task.FromResult(new ReadResponse(response));
}

public async Task&lt;ReadResponse&gt; MethodB(ReadRequest request)
{
    var response = await Task.FromResult(SynchronousReadThatTakesAWhileToExecute());
    return new ReadResponse(response);
}

I was thinking that MethodA would perform the slow method syncronously, and then, since it only uses await for a trivial constructor, would in practice be a synchronous method, which would block execution for other threads. My colleague argued that both would be the same in practice, and that an async method should always return a task (but I'm thinking that await already "unboxes" the Task, and that an asynch method only needs to have an await Task somewhere in it to be correct).

Thanks

答案1

得分: 2

Your colleague is right. The MethodA and MethodB are practically equivalent. They are both asynchronous facades with synchronous implementations. Every time these methods are called, the calling thread does all the work, and then is handed back an already completed task (.IsCompleted == true). The task contains either the result or an exception. The async keyword only helps at simplifying the result/exception wrapping. You could implement exactly the same behavior without async, with a little more code:

{
    try
    {
        var response = SynchronousReadThatTakesAWhileToExecute();
        return Task.FromResult(new ReadResponse(response));
    }
    catch (OperationCanceledException oce)
    {
        return Task.FromCanceled&lt;ReadResponse&gt;(oce.CancellationToken);
    }
    catch (Exception ex)
    {
        return Task.FromException&lt;ReadResponse&gt;(ex);
    }
}

The non-async method MethodC is intended for educational/demonstration purposes (it has a hidden bag). In practice using async for the wrapping is preferable.

英文:

Your colleague is right. The MethodA and MethodB are practically equivalent. They are both asynchronous facades with synchronous implementations. Every time these methods are called, the calling thread does all the work, and then is handed back an already completed task (.IsCompleted == true). The task contains either the result or an exception. The async keyword only helps at simplifying the result/exception wrapping. You could implement exactly the same behavior without async, with a little more code:

public Task&lt;ReadResponse&gt; MethodC(ReadRequest request)
{
    try
    {
        var response = SynchronousReadThatTakesAWhileToExecute();
        return Task.FromResult(new ReadResponse(response));
    }
    catch (OperationCanceledException oce)
    {
        return Task.FromCanceled&lt;ReadResponse&gt;(oce.CancellationToken);
    }
    catch (Exception ex)
    {
        return Task.FromException&lt;ReadResponse&gt;(ex);
    }
}

The non-async method MethodC is intended for educational/demonstration purposes (it has a hidden bag). In practice using async for the wrapping is preferable.

答案2

得分: 2

以下是代码部分的翻译:

// 做法一:如果可以使其异步,请这样做
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);
}
英文:

As others have pointed out, there's practically no difference between those methods.

However, both methods are fully synchronous even though they have an asynchronous signature. I do not recommend this, since it will be confusing to anyone using this code.

If you can make it asynchronous, then do so:

public async Task&lt;ReadRespone&gt; MethodAsync(ReadRequest request)
{
  var response = await AsynchronousReadThatTakesAWhileToExecuteAsync();
  return new ReadResponse(response);
}

If the work is just synchronous (and you can't make it asynchronous), then make your method synchronous:

public ReadRespone Method(ReadRequest request)
{
  var response = SynchronousReadThatTakesAWhileToExecute();
  return new ReadResponse(response);
}

If you have synchronous work (and you can't make it asynchronous), and your method must be asynchronous (i.e., it's implementing an interface method), then you can make a synchronous implementation of an asynchronous interface. Note that this code runs synchronously, which is surprising to consumers, so I recommend documenting the possibly-synchronous nature in the interface docs itself.

#pragma warning disable 1998
public async Task&lt;ReadRespone&gt; MethodAsync(ReadRequest request)
#pragma warning restore 1998
{
  var response = SynchronousReadThatTakesAWhileToExecute();
  return new ReadResponse(response);
}

答案3

得分: 1

以下是翻译好的代码部分:

public async Task<ReadResponse> Foo(ReadRequest request)
{
    var result = await Task.Run(SynchronousReadThatTakesAWhileToExecute);
    return new ReadResponse(result);
}

请让我知道如果您需要更多帮助。

英文:

As others have pointed out, Task.FromResult() doesn't really work like that. It takes a value and returns a completed task, with the value. You want to use Task.Run or TaskFactory.StartNew.

public async Task&lt;ReadResponse&gt; Foo(ReadRequest request)
{
    var result = await Task.Run(SynchronousReadThatTakesAWhileToExecute);
    return new ReadResponse(result);
}

Notice the lack of brackes after SynchronousReadThatTakesAWhileToExecute. This is because I am not calling the function directly but passing it to Task.Run instead. This is functionally equivalent to passing () =&gt; SynchronousReadThatTakesAWhileToExecute() to Task.Run as an argument.

Editing my response so that it actually answers the question:
The provided two options are pretty much identical performance-wise.

答案4

得分: 1

它们基本相同。两者都将执行阻塞工作,不会将控制权返回给调用方法。编译器生成的异步状态机将在任务已经完成时进行“快捷”操作,并实际上将继续执行当前方法。可以使用以下代码演示:

async Task FakeAsync()
{
    Console.WriteLine("Before IN FakeAsync");
    await Task.CompletedTask;
    Thread.Sleep(500); // "模拟" CPU 密集型工作
    Console.WriteLine("After IN FakeAsync");
}

async Task RealAsync()
{
    Console.WriteLine("Before IN RealAsync");
    await Task.Yield();
    Thread.Sleep(500); // "模拟" CPU 密集型工作
    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”)。

还要注意,Task.Yield技巧只在不存在同步上下文时起作用,因此它可以用于一些测试场景,对于“现实生活”来说,更好的方法是使用Task.Run

async Task RealAsync()
{
    Console.WriteLine("Before IN RealAsync");
    await Task.Run(() => Thread.Sleep(500)); // "模拟" CPU 密集型工作
    Console.WriteLine("After IN RealAsync");
}

阅读更多:

英文:

They are basically the same. Both will perform blocking work without giving the control back to the calling method. Async state machine generated by compiler will "shortcut" if the task has already finished and actually will continue executing the current method. This can be demonstrated with the following code:

async Task FakeAsync()
{
    Console.WriteLine(&quot;Before IN FakeAsync&quot;);
    await Task.CompletedTask;
    Thread.Sleep(500); // &quot;simulate&quot; CPU-bound work
    Console.WriteLine(&quot;After IN FakeAsync&quot;);
}

async Task RealAsync()
{
    Console.WriteLine(&quot;Before IN RealAsync&quot;);
    await Task.Yield();
    Thread.Sleep(500); // &quot;simulate&quot; CPU-bound work
    Console.WriteLine(&quot;After IN RealAsync&quot;);
}

Console.WriteLine(&quot;Before FakeAsync&quot;);
var task =  FakeAsync();
Console.WriteLine(&quot;After FakeAsync&quot;);
await task;
Console.WriteLine(&quot;After await FakeAsync&quot;);
Console.WriteLine(&quot;---------&quot;);
Console.WriteLine(&quot;Before RealAsync&quot;);
var task1 = RealAsync();
Console.WriteLine(&quot;After RealAsync&quot;);
await task1;
Console.WriteLine(&quot;After await RealAsync&quot;);

Which will give the following output:

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

Note that for the FakeAsync both "IN" statements are executed before the rest of the calling method, while for the RealAsync the control is given back to the calling method (hence the "After RealAsync" between the "IN"s)

Aslo bear in mind that Task.Yield trick will work when no synchronization context is present so it can be used for some test scenarios, for "real life" the better approach is Task.Run:

async Task RealAsync()
{
    Console.WriteLine(&quot;Before IN RealAsync&quot;);
    await Task.Run(() =&gt; Thread.Sleep(500)); // &quot;simulate&quot; CPU-bound work)
    Console.WriteLine(&quot;After IN RealAsync&quot;);
}

Read more:

huangapple
  • 本文由 发表于 2023年3月21日 02:03:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/75793800.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定