将"同步缓存机制"添加到"透明"的异步方法

Ami*_*ich 5 .net c# asynchronous task-parallel-library async-await

我有一个使用async task
Now 执行长动作的方法,我想添加一个在同一方法中透明的缓存机制.现在,我总是可以获取我的缓存结果并用一个Task包装它以便它"工作"但我想阻止我将获得的上下文切换.

这是我的一个例子:

var result = await LongTask();

private async Task<string> LongTask()
{
   return await DoSomethingLong();
}
Run Code Online (Sandbox Code Playgroud)

这是我想要的一个例子:

var result = await LongTask();

private async Task<string> LongTask()
{
   if(isInCache)
   { 
      return cachedValue(); // cache value is a const string you can do return "1" instead.
   }

   // else do the long thing and return my Task<string>
   return await DoSomethingLong();
}
Run Code Online (Sandbox Code Playgroud)

现在我很惊讶地看到这个编译和工作
Something告诉我,我没有正确地做到这一点.

这是我测试的另一个类似的例子:

private async Task<string> DownloadString(bool sync)
{
    using (WebClient wc = new WebClient())
    {
        var task = wc.DownloadStringTaskAsync("http://www.nba.com");

        if(sync)
            return task.Result;

        return await task;
    }
}
Run Code Online (Sandbox Code Playgroud)

这是代码:

var res = DownloadString(true);
string str1 = await res;
var res2 = DownloadString(false);
string str2 = await res2;
Run Code Online (Sandbox Code Playgroud)

从我在这里 阅读task.Result的内容同步执行任务并返回一个string.现在我通过Fiddler看到了请求,return task.Result即使我看到一个200 OK和我等了很长时间,我的程序仍然停留在线上.

底线:

  1. 什么是在异步方法中使用缓存的最佳\正确方法(例如,在某些情况下同步执行某些操作而不创建context switch overhead
  2. 为什么我的第二个代码块DownloadString被卡住了?

i3a*_*non 6

首先,如果在调用async方法后返回的任务已经完成,则不会有上下文切换,因为不需要任何上下文切换.所以这是完全可以接受的:

private async Task<string> LongTask()
{
   if(isInCache)
   { 
      return cachedValue(); // cache value is a const string you can do return "1" instead.
   }

   // else do the long thing and return my Task<string>
   return await DoSomethingLong();
}
Run Code Online (Sandbox Code Playgroud)

但是,在缓存结果的情况下,async机制是多余的.这种开销是微不足道的大部分,但你可以通过删除既提高性能asyncawait并使用一张已完成的任务Task.FromResult:

private Task<string> LongTask()
{
   if(isInCache)
   { 
      return Task.FromResult(cachedValue()); 
   }

   // else do the long thing and return my Task<string>
   return DoSomethingLong();
}
Run Code Online (Sandbox Code Playgroud)

...当您编写"await someObject;"时,编译器将生成检查someObject表示的操作是否已完成的代码.如果有,则执行在等待点上同步继续.如果没有,生成的代码将连续委托连接到等待的对象,这样当表示的操作完成时,将调用该连续委托

来自Async/Await FAQ


Task.Result不同步执行任务,它同步等待.这意味着调用线程被阻塞,等待任务完成.当您在具有SynchronizationContext可能导致死锁的环境中使用它时,因为线程被阻止并且无法处理任务的完成.您不应该Result在尚未完成的任务上使用该属性.

  • 请注意,`Task.FromResult`将创建一个新的`Task`包装该值,然后由`await`立即解包.这确实会导致额外的内存压力.因此,我更喜欢缓存*task*本身,它有两个好处:1)当值已经在缓存中时没有分配内存,2)很容易同时请求同一个项目使用相同的内容任务,即使它不在缓存中. (3认同)