Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are the risks of wrapping Async/Await IAsyncOperations with Task.Wait() code?

I'm currently trying to port a fair amount of existing synchronous code to WinRT.

As part of this, I'm hitting problems with the existing code expecting some operations to be synchronous - e.g. for file I/O

To adapt this existing code to work with the IAsyncOperation style API within WinRT, I've used a technique of wrapping the IAsyncOperation with an extension method like:

namespace Cirrious.MvvmCross.Plugins.File.WinRT
{
    public static class WinRTExtensionMethods
    {
        public static TResult Await<TResult>(this IAsyncOperation<TResult> operation)
        {
            var task = operation.AsTask();
            task.Wait();
            if (task.Exception != null)
            {
                // TODO - is this correct?
                throw task.Exception.InnerException;
            }

            return task.Result;
        }
    }
}

from MvvmCross WinRT ExtensionMethods - with a similar method for IAsyncAction

These wrappers seems to work - and they allow me to use the Async methods in synchronous code like:

    public IEnumerable<string> GetFilesIn(string folderPath)
    {
        var folder = StorageFolder.GetFolderFromPathAsync(ToFullPath(folderPath)).Await();
        var files = folder.GetFilesAsync().Await();
        return files.Select(x => x.Name);
    }

I understand that this isn't really in the spirit of WinRT; but I am expecting these methods to normally only get called on background threads in the first place; and I am writing this with the goal of making my code cross-platform compatible - including to platforms which don't yet support await-async and/or to developers who aren't yet ready to make the jump.

So... the question is: what risks am I running by using this type of code?

And as a second question, is there any better way I could achieve code reuse for areas such as File I/O?

like image 216
Stuart Avatar asked May 19 '12 19:05

Stuart


2 Answers

First, I think your method could be rewritten as:

public static TResult Await<TResult>(this IAsyncOperation<TResult> operation)
{
    return operation.AsTask().Result;
}

Calling Result will synchronously wait if the task didn't finish yet. And it will throw an AgreggateException if it fails. I think throwing the InnerException like you do is a bad idea, because it overwrites the stack trace of the exception.

Regarding your actual question, I think the biggest danger with using Wait() together with asynchronous code are deadlocks. If you start some operation that internally uses await on the UI thread and then you wait for it using Wait() on the same thread, you will get a deadlock.

This is not as important if you're not Wait()ing on the UI thread, but you should still avoid it if possible, because it goes against the whole async idea.

like image 133
svick Avatar answered Oct 21 '22 01:10

svick


There's a lot of good reasons not to do this. See for example http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx

But if you want to do it, use the GetResults() method as follows

public static TResult Await<TResult>(this IAsyncOperation<TResult> operation)
{
    try
    {
        return operation.GetResults();
    }
    finally
    {
        operation.Close();
    }
}  

Wrapping the IAsyncOperation in a task, as svick mentioned, works too but is less efficient.

like image 39
Kris Vandermotten Avatar answered Oct 21 '22 00:10

Kris Vandermotten