14

I have a class in charge of retrieving resources which also caches them for quick access. The class exposes an asynchronous method for retrieving a resource:

public Task<object> GetResourceAsync(string resourceName)
{
    return Task.Factory.StartNew<object>(() =>
    {
        // look in cache

        // if not found, get from disk

        // return resource
    });
}

The client code then looks like this:

myResourceProvider.GetResourceAsync("myResource")
    .ContinueWith<object>(t => Console.WriteLine("Got resource " + t.Result.ToString()));

This way, a background thread is always used. However, I don't want the code to run asynchronously if the object was found in the cache. If it was found in the cache, I'd like to immediately return the resource and not to have to use another thread.

Thanks.

Adi Lester
  • 24,731
  • 12
  • 95
  • 110

2 Answers2

24

.NET 4.5 has Task.FromResult that lets you return a Task<T>, but instead of running a delegate on a threadpool thread, it explicitly sets the task's return value.

So in the context of your code:

public Task<object> AsyncGetResource(string resourceName)
{
    object valueFromCache;
    if (_myCache.TryGetValue(resourceName, out valueFromCache)) {
        return Task.FromResult(valueFromCache);
    }
    return Task.Factory.StartNew<object>(() =>
    {
        // get from disk
        // add to cache
        // return resource
    });
}

If you're still on .NET 4.0, you can use TaskCompletionSource<T> to do the same thing:

var tcs = new TaskCompletionSource<object>();
tcs.SetResult(...item from cache...);
return tcs.Task;
Joe White
  • 94,807
  • 60
  • 220
  • 330
  • +1 I was asking this question (http://stackoverflow.com/questions/15316613/real-life-scenarios-for-using-taskcompletionsourcet) and Now I can see when should I use it. – Royi Namir Mar 10 '13 at 06:38
  • @joe can you please enhance this code ( append a solution) for Task.FromResult ? – Royi Namir Jun 27 '13 at 07:44
  • 4
    Calling `Task.Factory.StartNew` by default uses `TaskScheduler.Current`, this in some situations can cause unexpected behavior where a task started by StartNew runs on the UI thread. You should always pass in `TaskScheduler.Default` or use `Task.Run` if it is available to you if you intend your code to always run on a background thread. – Scott Chamberlain Jun 16 '15 at 16:55
2

Be careful if you have UI connected thread.

In WPF it is very important, to use the Task.Run on the UI thread (eg. a button click event handler), to avoid UI problems, and running your code on a background thread.

Why? The Task.Run by default a wrapper around the Task.Factory.StartNew with the TaskScheduler.Default parameter, instead of TaskScheduler.Current.

So not enough just to call your async method like this, because this is running on the UI thread and freezing it: await SomeTaskAsync();

Instead of it you should call it inside a Task.Run:

  • Task.Run(async() => await SomeTaskAsync());

Or use your syncron method in the Task.Run:

  • Task.Run(() => SomeTask());
Bence Végert
  • 728
  • 10
  • 12