4

I am implementing an interface method that is asynchronous (returns Task). However, my implementation is by necessity synchronous. What's the best way to do this? Is there some built-in way to do this? Here are several options I'm considering:

  • Option 1: Task.FromResult

    return Task.FromResult(ComputeResult());
    

This is good because my code runs synchronously. The disadvantage is that if ComputeResult() fails or is canceled my method throws instead of returning a failed task.

  • Option 2: Task.Run

    return Task.Run(() => ComputeResult());
    

This propagates failure and cancellation more naturally. However, this also introduces an unnecessary thread-hop.

  • Option 3: TaskCompletionSource

    var tcs = new TaskCompletionSource<T>();
    try 
    { 
        tcs.SetResult(ComputeResult()); 
    }
    catch (OperationCanceledException) 
    { 
        tcs.SetCanceled(); 
    }
    catch (Exception ex) 
    { 
        tcs.SetException(ex); 
    }
    
    return tcs.Task;
    

This both propagates failure/cancellation AND avoids the thread-hop, but it's more verbose and complex.

Mike Zboray
  • 39,828
  • 3
  • 90
  • 122
ChaseMedallion
  • 20,860
  • 17
  • 88
  • 152
  • Check some of the answers on [this question](http://stackoverflow.com/questions/28929647/to-task-run-or-not-to-task-run). – Joe Enos Jun 15 '15 at 18:07
  • 2
    Only the second option is actually asynchronous for the caller. The question is how bad is it to block the calling thread. Presumably the interface uses tasks so that the caller is not blocked. – Mike Zboray Jun 15 '15 at 18:07
  • what do you mean by 'thread-hop' ? – eran otzap Jun 15 '15 at 18:08
  • Is the result completed *quickly* and synchronously? If you can compute the result very fast, `FromResult` is likely fine. If it's going to take a long time to compute the result then, given that the caller is expecting the operation to be asynchronous and is likely relying on that, blocking it is likely going to cause problems. – Servy Jun 15 '15 at 18:09
  • @eranotzap: with Thread.Run(), my understanding is that the work won't run on the calling thread, but on another thread, which is unnecessary in the case where the caller always awaits the task. – ChaseMedallion Jun 15 '15 at 18:09
  • I didn't understand when it is unnecessary .. – eran otzap Jun 15 '15 at 18:12
  • Is there a reason why `ComputeResult().Wait()` or `ComputeResult().RunSynchronously()` doesn't work? – whoisj Jun 15 '15 at 18:12
  • @whoisj, `ComputeResult` doesn't return a `Task`, AFAIU. – noseratio Jun 15 '15 at 18:21
  • @ChaseMedallion No, it's not unnecessary. Presumably the caller wants this operation to be done asynchronously for a reason, which is why it's expecting a `Task` in the first place. If it didn't require the work to be done asynchronously, and wanted the work done synchronously, it'd just accept the result, not a `Task`. – Servy Jun 15 '15 at 18:21

1 Answers1

2

Two other options you might have overlooked:

  • just make your method async and do return ComputeResult(). Suppress the compiler warning with pragma. If you don't like to suppress the warning, you could do this:

    async Task<Result> ComputeResultAsync()
    {
        await Task.FromResult(0);
        return ComputeResult();
    }
    
  • use Task.RunSynchronously:

    Task<Result> ComputeResultAsync()
    {
        var task = new Task<Result>(() => ComputeResult());
        task.RunSynchronously(TaskScheduler.Default);
        return task;
    }
    

The latter will provide exception propagation similar to the async method. Note however, under certain conditions (like when it's too deep on the stack), RunSynchronously could still execute asynchronously.

noseratio
  • 59,932
  • 34
  • 208
  • 486