How to conditionally run a code asynchonously usin

2019-03-15 13:23发布

问题:

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.

回答1:

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


回答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());