5

I have a method which does a long action using an async task
Now, I want to add a cache mechanism that will be transparent in the same method. Now, I could always fetch my cache result and wrap it with a Task so it will "work" but I want to prevent the context switch that I will get.

Here's an example of what I have:

var result = await LongTask();

private async Task<string> LongTask()
{
   return await DoSomethingLong();
}

And here's an example of what I want:

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

Now I'm surprised to see that this compiled and worked
Something tells me that I'm not doing it correctly.

Here's another similar example that I've tested:

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

And here's the code:

var res = DownloadString(true);
string str1 = await res;
var res2 = DownloadString(false);
string str2 = await res2;

From what I've read here task.Result executes the task synchronously and returns a string. Now I see the request via Fiddler and my program get's stuck on the return task.Result line even though I see a 200 OK and I wait a long time.

Bottom Line:

  1. Whats the best\correct way to use caching inside an async method(e.g. doing something synchronously in some cases without create a context switch overhead?
  2. Why does my second block of code with the DownloadString get stuck?
Community
  • 1
  • 1
Amir Popovich
  • 29,350
  • 9
  • 53
  • 99

1 Answers1

6

First of all, if after a call to an async method the returned task is already completed there would be no context switch, because none is needed. So this is completely acceptable:

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

However, in the cases where the result is cached, the async mechanism is redundant. This overhead is mostly negligible but you can improve performance by dropping both the async and await and create a completed task using Task.FromResult:

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

   // else do the long thing and return my Task<string>
   return DoSomethingLong();
}

...when you write “await someObject;” the compiler will generate code that checks whether the operation represented by someObject has already completed. If it has, execution continues synchronously over the await point. If it hasn’t, the generated code will hook up a continuation delegate to the awaited object such that when the represented operation completes, that continuation delegate will be invoked

From Async/Await FAQ


Task.Result doesn't execute the task synchronously, it waits synchronously. That means that the calling thread is blocked waiting for the task to complete. When you use that in an environment with a SynchronizationContext that may lead to a deadlock since the thread is blocked and can't handle the task's completion. You shouldn't use the Result property on a task that hasn't completed yet.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • +1. Thanks for your answer. Using the second code block will need an await outside the method( e.g. var res = await LongTask() ). So how does that improve things here? I still don't really understand why there's an overhead with the first code block(even though it seems wrong to do by me). Will a mix up be better? if(isInCache) task = Task.FromResult(cachedValue()); than the first case? – Amir Popovich Oct 19 '14 at 13:13
  • @AmirPopovich `await` has no cost if the task being awaited has already completed (which is the case where the result is cached). Marking a method as `async` however has a small cost even if there's no await in it, because the compiler builds a state machine behind the scenes. – i3arnon Oct 19 '14 at 13:27
  • @AmirPopovich I actually just did in the answer. Here's another: http://msdn.microsoft.com/en-us/library/hh191443.aspx – i3arnon Oct 19 '14 at 13:30
  • 3
    Note that `Task.FromResult` will create a new `Task` wrapping the value which is then immediately unwrapped by `await`. This does cause additional memory pressure. So, I prefer to cache the *task* itself, which has two benefits: 1) there's no memory allocated when the value is already in the cache, and 2) it's easy to have two simultaneous requests for the same item to use the same task, even if it's not in the cache. – Stephen Cleary Oct 20 '14 at 01:19