C# 什么时候是使用Task.Result而不是等待Task的最佳位置

C# 什么时候是使用Task.Result而不是等待Task的最佳位置,c#,.net,asynchronous,async-await,C#,.net,Asynchronous,Async Await,虽然我在.NET中使用异步代码已经有一段时间了,但我只是最近才开始研究它并了解发生了什么。我刚刚检查了我的代码并试图修改它,所以如果一个任务可以与一些工作并行完成,那么它就是。例如: var user = await _userRepo.GetByUsername(User.Identity.Name); //Some minor work that doesn't rely on the user object user = await _userRepo.UpdateLastAccess

虽然我在.NET中使用异步代码已经有一段时间了,但我只是最近才开始研究它并了解发生了什么。我刚刚检查了我的代码并试图修改它,所以如果一个任务可以与一些工作并行完成,那么它就是。例如:

var user = await _userRepo.GetByUsername(User.Identity.Name);

//Some minor work that doesn't rely on the user object

user = await _userRepo.UpdateLastAccessed(user, DateTime.Now);

return user;
现在变成:

var userTask = _userRepo.GetByUsername(User.Identity.Name);

//Some work that doesn't rely on the user object

user = await _userRepo.UpdateLastAccessed(userTask.Result, DateTime.Now);

return user;

我的理解是,用户对象现在正在从数据库中提取,而一些无关的工作正在进行。然而,我在帖子中看到的情况表明,result应该很少使用,而wait是首选,但我不明白,如果我可以同时执行一些其他独立逻辑,为什么我要等待获取我的用户对象?

您考虑过这个版本吗

var userTask = _userRepo.GetByUsername(User.Identity.Name);

//Some work that doesn't rely on the user object

user = await _userRepo.UpdateLastAccessed(await userTask, DateTime.Now);

return user;
这将在检索用户时执行“工作”,但它还具有中所述的
wait
的所有优点


正如建议的那样,您还可以使用更显式的版本,以便能够在调试器中检查调用的结果

var userTask = _userRepo.GetByUsername(User.Identity.Name);

//Some work that doesn't rely on the user object

user = await userTask;
user = await _userRepo.UpdateLastAccessed(user, DateTime.Now);

return user;

在您的情况下,您可以使用:

user = await _userRepo.UpdateLastAccessed(await userTask, DateTime.Now);
或者更清楚地说:

var user = await _userRepo.GetByUsername(User.Identity.Name);
//Some work that doesn't rely on the user object
user = await _userRepo.UpdateLastAccessed(user, DateTime.Now);
只有当您知道任务已完成时,才应触摸
.Result
。在某些情况下,如果您试图避免创建
async
状态机,并且您认为任务很有可能已同步完成(可能对
async
案例使用本地函数),或者如果您使用回调而不是
async
/
wait
,则此功能非常有用,你就在回拨电话里

作为避免使用状态机的示例:

ValueTask<int> FetchAndProcess(SomeArgs args) {
    async ValueTask<int> Awaited(ValueTask<int> task) => SomeOtherProcessing(await task);
    var task = GetAsyncData(args);
    if (!task.IsCompletedSuccessfully) return Awaited(task);
    return new ValueTask<int>(SomeOtherProcessing(task.Result));
}
ValueTask FetchAndProcess(SomeArgs-args){
等待异步ValueTask(ValueTask任务)=>SomeOtherProcessing(等待任务);
var task=GetAsyncData(args);
如果(!task.IsCompletedSuccessfully)返回等待(task);
返回新的ValueTask(SomeOtherProcessing(task.Result));
}

这里的要点是,如果
GetAsyncData
返回一个同步完成的结果,我们将完全避免所有
async
机制。

异步等待并不意味着几个线程将运行您的代码。

但是,这将减少线程空闲等待进程完成的时间,从而提前完成

每当线程通常必须等待某件事情完成时,比如等待网页下载、数据库查询完成、磁盘写入完成,异步等待线程不会等待数据写入/获取,而是四处查看是否可以执行其他操作,等待任务完成后再回来

这一点在本书中用库克的比喻进行了描述。在中间搜索某处,等待。< /P> Eric Lippert将async await与一个必须做早餐的(!)厨师进行了比较。在他开始烤面包后,他可以无所事事地等到面包烤好后再放上茶壶,等到水开了再把茶叶放进茶壶,等等

一个异步等待厨师,不会等待烤面包,而是放上水壶,在水加热时,他会把茶叶放进茶壶

每当厨师不得不无所事事地等待某事时,他就会环顾四周,看看是否可以做些别的事情

异步函数中的线程将执行类似的操作。因为函数是异步的,所以您知道函数中存在等待。事实上,如果您忘记编写wait,编译器将警告您

当您的线程遇到等待时,它会向上移动其调用堆栈以查看是否可以执行其他操作,直到它看到等待,然后再次向上移动调用堆栈,等等。一旦每个人都在等待,他就会向下移动调用堆栈并开始无所事事地等待,直到第一个等待的进程完成

等待过程完成后,线程将在等待之后继续处理语句,直到再次看到等待

可能是另一个线程将继续处理等待之后的语句(通过检查线程ID,可以在调试器中看到这一点)。但是,另一个线程具有原始线程的上下文,因此它可以像原始线程一样工作。不需要互斥、信号量、IsInvokeRequired(在winforms中)等。对您来说,似乎只有一个线程

有时,你的厨师不得不做一些不需要等待的事情,比如切西红柿。在这种情况下,聘请另一名厨师并命令他做切片可能是明智的。与此同时,你的厨师可以继续煮刚刚煮好需要剥皮的鸡蛋

用计算机术语来说,这就是如果你不用等待其他过程就进行一些大的计算。请注意其中的区别,例如将数据写入磁盘。一旦线程命令将数据写入磁盘,它通常会等待数据写入磁盘。在进行大型计算时,情况并非如此

您可以使用
任务雇佣额外的厨师。运行

async Task<TimeSpan> CalculateSunSet()
{
    // start fetching sunset data. however don't wait for the result yet
    // you've got better things to do:
    Task<SunsetData> taskFetchData = FetchSunsetData();

    // because you are not awaiting your thread will do the following:
    Location location = FetchLocation();

    // now you need the sunset data, start awaiting for the Task:
    SunsetData sunsetData = await taskFetchData;

    // some big calculations are needed, that take 33 seconds,
    // you want to keep your caller responsive, so start a Task
    // this Task will be run by a different thread:
    ask<DateTime> taskBigCalculations = Taks.Run( () => BigCalculations(sunsetData, location);

    // again no await: you are still free to do other things
    ...
    // before returning you need the result of the big calculations.
    // wait until big calculations are finished, keep caller responsive:
    DateTime result = await taskBigCalculations;
    return result;
}
异步任务CalculateSunSet()
{
//开始获取日落数据。但是不要等待结果
//你有更好的事情要做:
TaskTaskFetchData=FetchSunsetData();
//由于您没有等待线程,因此将执行以下操作:
Location=FetchLocation();
//现在需要日落数据,开始等待任务:
SunsetData SunsetData=等待任务获取数据;
//需要一些大的计算,需要33秒,
//你想让你的来电者反应迅速,所以开始一项任务
//此任务将由其他线程运行:
ask taskBigCalculations=Taks.Run(()=>BigCalculations(sunsetData,位置);
//再次无需等待:你仍然可以自由地做其他事情
...
//在返回之前,您需要大计算的结果。
//等到大的计算完成后,让来电者重新开始
var userTask = _userRepo.GetByUsername(User.Identity.Name);    
//Some work that doesn't rely on the user object    
user = await _userRepo.UpdateLastAccessed(await userTask, DateTime.Now);    
return user;