How to write an async method with out parameter?
Asked Answered
S

15

307

I want to write an async method with an out parameter, like this:

public async void Method1()
{
    int op;
    int result = await GetDataTaskAsync(out op);
}

How do I do this in GetDataTaskAsync?

Slype answered 10/9, 2013 at 10:50 Comment(0)
S
419

You can't have async methods with ref or out parameters.

Lucian Wischik explains why this is not possible on this MSDN thread: http://social.msdn.microsoft.com/Forums/en-US/d2f48a52-e35a-4948-844d-828a1a6deb74/why-async-methods-cannot-have-ref-or-out-parameters (Original post is no longer available due to link rot, latest archived version can be found here.)

As for why async methods don't support out-by-reference parameters? (or ref parameters?) That's a limitation of the CLR. We chose to implement async methods in a similar way to iterator methods -- i.e. through the compiler transforming the method into a state-machine-object. The CLR has no safe way to store the address of an "out parameter" or "reference parameter" as a field of an object. The only way to have supported out-by-reference parameters would be if the async feature were done by a low-level CLR rewrite instead of a compiler-rewrite. We examined that approach, and it had a lot going for it, but it would ultimately have been so costly that it'd never have happened.

A typical workaround for this situation is to have the async method return a Tuple instead. You could re-write your method as such:

public async Task Method1()
{
    var tuple = await GetDataTaskAsync();
    int op = tuple.Item1;
    int result = tuple.Item2;
}

public async Task<Tuple<int, int>> GetDataTaskAsync()
{
    //...
    return new Tuple<int, int>(1, 2);
}
Speedwell answered 10/9, 2013 at 10:51 Comment(6)
Far from being too complex, this could produce too many problem. Jon Skeet explained it very well here #20868603Witty
I think Named Tuples in C# 7 will be the perfect solution for this.Metallize
@Metallize I especially like this: private async Task<(bool success, Job job, string message)> TryGetJobAsync(...)Salena
why doesn't the compiler create the tuple behind the scenes?Tini
Tuple return values, named or not, are a code smell in my book because they tend to accumulate new members until they become unwieldy. Plus you have no inheritance chain, so if you want to write code to operate on the differently-shaped tuple return values of your methods, you're out of luck (and you also have the opposite problem that your code isn't specific enough to your methods). tl;dr using tuples as retvals is barely a step up from dynamic - C# is an OOP language so use a proper class to allow inheritance, e.g. https://mcmap.net/q/67907/-how-to-implement-the-trydosomething-pattern-with-async-duplicateNolita
To be more succinct, you could unpack the tuple in one line: var (op, result) = await GetDataTaskAsync();Methacrylate
S
110

The C#7+ Solution is to use implicit tuple syntax.

    private async Task<(bool IsSuccess, IActionResult Result)> TryLogin(OpenIdConnectRequest request)
    { 
        return (true, BadRequest(new OpenIdErrorResponse
        {
            Error = OpenIdConnectConstants.Errors.AccessDenied,
            ErrorDescription = "Access token provided is not valid."
        }));
    }

return result utilizes the method signature defined property names. e.g:

var foo = await TryLogin(request);
if (foo.IsSuccess)
     return foo.Result;
Spleenwort answered 27/4, 2018 at 14:30 Comment(2)
why are you hardcoding "true" as your "isSuccess" return value but returning BadRequest as the result, though?Huba
Returning a named tuple of this form is very similar to returning an Either type, which are common in functional languages. Haskell has one, for example: hackage.haskell.org/package/base-4.18.0.0/docs/Data-Either.html. The LanguageExt package for C# has one too (and a lot of other functional extensions): louthy.github.io/language-ext/LanguageExt.Core/Monads/…. Another alternative would be to return an Option type, if you expect 0 or 1 things to come back (i.e. for handling TryGetValue on a Dictionary, for example).Hasen
F
65

You cannot have ref or out parameters in async methods (as was already noted).

This screams for some modelling in the data moving around:

public class Data
{
    public int Op {get; set;}
    public int Result {get; set;}
}

public async void Method1()
{
    Data data = await GetDataTaskAsync();
    // use data.Op and data.Result from here on
}

public async Task<Data> GetDataTaskAsync()
{
    var returnValue = new Data();
    // Fill up returnValue
    return returnValue;
}

You gain the ability to reuse your code more easily, plus it's way more readable than variables or tuples.

Flick answered 10/9, 2013 at 11:47 Comment(1)
I prefer this solution instead using a Tuple. More clean!Tenebrous
S
33

I had the same problem as I like using the Try-method-pattern which basically seems to be incompatible to the async-await-paradigm...

Important to me is that I can call the Try-method within a single if-clause and do not have to pre-define the out-variables before, but can do it in-line like in the following example:

if (TryReceive(out string msg))
{
    // use msg
}

So I came up with the following solutions:

Note: The new solution is superior, because it can be used with methods that simply return a tuple as described in many of the other answers here, what might often be found in existing code!

New solution:

  1. Create extension methods for ValueTuples:

    public static class TupleExtensions
    {
      public static bool TryOut<P2>(this ValueTuple<bool, P2> tuple, out P2 p2)
      {
         bool p1;
         (p1, p2) = tuple;
         return p1;
      }
    
      public static bool TryOut<P2, P3>(this ValueTuple<bool, P2, P3> tuple, out P2 p2, out P3 p3)
      {
         bool p1;
         (p1, p2, p3) = tuple;
         return p1;
      }
    
      // continue to support larger tuples...
    }
    
  2. Define async Try-method like this:

     public async Task<(bool, string)> TryReceiveAsync()
     {
         string message;
         bool success;
         // ...
         return (success, message);
     }
    
  3. Call the async Try-method like this:

     if ((await TryReceiveAsync()).TryOut(out string msg))
     {
         // use msg
     }
    

Old solution:

  1. Define a helper struct:

     public struct AsyncOut<T, OUT>
     {
         private readonly T returnValue;
         private readonly OUT result;
    
         public AsyncOut(T returnValue, OUT result)
         {
             this.returnValue = returnValue;
             this.result = result;
         }
    
         public T Out(out OUT result)
         {
             result = this.result;
             return returnValue;
         }
    
         public T ReturnValue => returnValue;
    
         public static implicit operator AsyncOut<T, OUT>((T returnValue ,OUT result) tuple) => 
             new AsyncOut<T, OUT>(tuple.returnValue, tuple.result);
     }
    
  2. Define async Try-method like this:

     public async Task<AsyncOut<bool, string>> TryReceiveAsync()
     {
         string message;
         bool success;
         // ...
         return (success, message);
     }
    
  3. Call the async Try-method like this:

     if ((await TryReceiveAsync()).Out(out string msg))
     {
         // use msg
     }
    

For multiple out parameters you can define additional structs (e.g. AsyncOut<T,OUT1, OUT2>) or you can return a tuple.

Selfaggrandizement answered 29/8, 2019 at 9:59 Comment(2)
How is this not one of the top answers, this solution provides parity with the non async version unlike other anwsers that have you return a tuple and then run your if block.Methionine
Fyi, this is the only answer here that is "in keeping" with the non-async Try pattern. The use of Tuples and ValueTuples is not at all in keeping.Selima
U
16

Alex made a great point on readability. Equivalently, a function is also interface enough to define the type(s) being returned and you also get meaningful variable names.

delegate void OpDelegate(int op);
Task<bool> GetDataTaskAsync(OpDelegate callback)
{
    bool canGetData = true;
    if (canGetData) callback(5);
    return Task.FromResult(canGetData);
}

Callers provide a lambda (or a named function) and intellisense helps by copying the variable name(s) from the delegate.

int myOp;
bool result = await GetDataTaskAsync(op => myOp = op);

This particular approach is like a "Try" method where myOp is set if the method result is true. Otherwise, you don't care about myOp.

Unseen answered 11/9, 2015 at 4:35 Comment(0)
E
11

I love the Try pattern. It's a tidy pattern.

if (double.TryParse(name, out var result))
{
    // handle success
}
else
{
    // handle error
}

But, it's challenging with async. That doesn't mean we don't have real options. Here are the three core approaches you can consider for async methods in a quasi-version of the Try pattern.

Approach 1 - output a structure

This looks most like a sync Try method only returning a tuple instead of a bool with an out parameter, which we all know is not permitted in C#.

var result = await DoAsync(name);
if (result.Success)
{
    // handle success
}
else
{
    // handle error
}

With a method that returns true of false and never throws an exception.

Remember, throwing an exception in a Try method breaks the whole purpose of the pattern.

async Task<(bool Success, StorageFile File, Exception exception)> DoAsync(string fileName)
{
    try
    {
        var folder = ApplicationData.Current.LocalCacheFolder;
        return (true, await folder.GetFileAsync(fileName), null);
    }
    catch (Exception exception)
    {
        return (false, null, exception);
    }
}

Approach 2 - pass in callback methods

We can use anonymous methods to set external variables. It's clever syntax, though slightly complicated. In small doses, it's fine.

var file = default(StorageFile);
var exception = default(Exception);
if (await DoAsync(name, x => file = x, x => exception = x))
{
    // handle success
}
else
{
    // handle failure
}

The method obeys the basics of the Try pattern but sets out parameters to passed in callback methods. It's done like this.

async Task<bool> DoAsync(string fileName, Action<StorageFile> file, Action<Exception> error)
{
    try
    {
        var folder = ApplicationData.Current.LocalCacheFolder;
        file?.Invoke(await folder.GetFileAsync(fileName));
        return true;
    }
    catch (Exception exception)
    {
        error?.Invoke(exception);
        return false;
    }
}

There's a question in my mind about performance here. But, the C# compiler is so freaking smart, that I think you're safe choosing this option, almost for sure.

Approach 3 - use ContinueWith

What if you just use the TPL as designed? No tuples. The idea here is that we use exceptions to redirect ContinueWith to two different paths.

await DoAsync(name).ContinueWith(task =>
{
    if (task.Exception != null)
    {
        // handle fail
    }
    if (task.Result is StorageFile sf)
    {
        // handle success
    }
});

With a method that throws an exception when there is any kind of failure. That's different than returning a boolean. It's a way to communicate with the TPL.

async Task<StorageFile> DoAsync(string fileName)
{
    var folder = ApplicationData.Current.LocalCacheFolder;
    return await folder.GetFileAsync(fileName);
}

In the code above, if the file is not found, an exception is thrown. This will invoke the failure ContinueWith that will handle Task.Exception in its logic block. Neat, huh?

Listen, there's a reason we love the Try pattern. It's fundamentally so neat and readable and, as a result, maintainable. As you choose your approach, watchdog for readability. Remember the next developer who in 6 months and doesn't have you to answer clarifying questions. Your code can be the only documentation a developer will ever have.

Best of luck.

Espalier answered 29/9, 2019 at 19:41 Comment(5)
About the third approach, are you sure that chaining ContinueWith calls has the expected outcome? According to my understanding the second ContinueWith will check the success of the first continuation, not the success of the original task.Specter
Throwing exceptions for flow control is a massive code smell for me - it's going to tank your performance.Nolita
No, @IanKemp, that's a pretty old concept. The compiler has evolved.Espalier
I really like approach 2 here.Benil
It's 2021 and throwing exceptions for flow control is still bad for performance; .NET Core maintainers consider any caught first-chance exceptions to be addressed if at all possible.Coach
G
9

One nice feature of out parameters is that they can be used to return data even when a function throws an exception. I think the closest equivalent to doing this with an async method would be using a new object to hold the data that both the async method and caller can refer to. Another way would be to pass a delegate as suggested in another answer.

Note that neither of these techniques will have any of the sort of enforcement from the compiler that out has. I.e., the compiler won’t require you to set the value on the shared object or call a passed in delegate.

Here’s an example implementation using a shared object to imitate ref and out for use with async methods and other various scenarios where ref and out aren’t available:

class Ref<T>
{
    // Field rather than a property to support passing to functions
    // accepting `ref T` or `out T`.
    public T Value;
}

async Task OperationExampleAsync(Ref<int> successfulLoopsRef)
{
    var things = new[] { 0, 1, 2, };
    var i = 0;
    while (true)
    {
        // Fourth iteration will throw an exception, but we will still have
        // communicated data back to the caller via successfulLoopsRef.
        things[i] += i;
        successfulLoopsRef.Value++;
        i++;
    }
}

async Task UsageExample()
{
    var successCounterRef = new Ref<int>();
    // Note that it does not make sense to access successCounterRef
    // until OperationExampleAsync completes (either fails or succeeds)
    // because there’s no synchronization. Here, I think of passing
    // the variable as “temporarily giving ownership” of the referenced
    // object to OperationExampleAsync. Deciding on conventions is up to
    // you and belongs in documentation ^^.
    try
    {
        await OperationExampleAsync(successCounterRef);
    }
    finally
    {
        Console.WriteLine($"Had {successCounterRef.Value} successful loops.");
    }
}
Gagliano answered 23/6, 2016 at 20:3 Comment(0)
B
7

Here's the code of @dcastro's answer modified for C# 7.0 with named tuples and tuple deconstruction, which streamlines the notation:

public async void Method1()
{
    // Version 1, named tuples:
    // just to show how it works
    /*
    var tuple = await GetDataTaskAsync();
    int op = tuple.paramOp;
    int result = tuple.paramResult;
    */

    // Version 2, tuple deconstruction:
    // much shorter, most elegant
    (int op, int result) = await GetDataTaskAsync();
}

public async Task<(int paramOp, int paramResult)> GetDataTaskAsync()
{
    //...
    return (1, 2);
}

For details about the new named tuples, tuple literals and tuple deconstructions see: https://blogs.msdn.microsoft.com/dotnet/2017/03/09/new-features-in-c-7-0/

Bridewell answered 13/6, 2018 at 13:26 Comment(0)
S
6

The limitation of the async methods not accepting out parameters applies only to the compiler-generated async methods, these declared with the async keyword. It doesn't apply to hand-crafted async methods. In other words it is possible to create Task returning methods accepting out parameters. For example lets say that we already have a ParseIntAsync method that throws, and we want to create a TryParseIntAsync that doesn't throw. We could implement it like this:

public static Task<bool> TryParseIntAsync(string s, out Task<int> result)
{
    var tcs = new TaskCompletionSource<int>();
    result = tcs.Task;
    return ParseIntAsync(s).ContinueWith(t =>
    {
        if (t.IsFaulted)
        {
            tcs.SetException(t.Exception.InnerException);
            return false;
        }
        tcs.SetResult(t.Result);
        return true;
    }, default, TaskContinuationOptions.None, TaskScheduler.Default);
}

Using the TaskCompletionSource and the ContinueWith method is a bit awkward, but there is no other option since we can't use the convenient await keyword inside this method.

Usage example:

if (await TryParseIntAsync("-13", out var result))
{
    Console.WriteLine($"Result: {await result}");
}
else
{
    Console.WriteLine($"Parse failed");
}

Update: If the async logic is too complex to be expressed without await, then it could be encapsulated inside a nested asynchronous anonymous delegate. A TaskCompletionSource would still be needed for the out parameter. It is possible that the out parameter could be completed before the completion of the main task, as in the example bellow:

public static Task<string> GetDataAsync(string url, out Task<int> rawDataLength)
{
    var tcs = new TaskCompletionSource<int>();
    rawDataLength = tcs.Task;
    return ((Func<Task<string>>)(async () =>
    {
        var response = await GetResponseAsync(url);
        var rawData = await GetRawDataAsync(response);
        tcs.SetResult(rawData.Length);
        return await FilterDataAsync(rawData);
    }))();
}

This example assumes the existence of three asynchronous methods GetResponseAsync, GetRawDataAsync and FilterDataAsync that are called in succession. The out parameter is completed on the completion of the second method. The GetDataAsync method could be used like this:

var data = await GetDataAsync("http://example.com", out var rawDataLength);
Console.WriteLine($"Data: {data}");
Console.WriteLine($"RawDataLength: {await rawDataLength}");

Awaiting the data before awaiting the rawDataLength is important in this simplified example, because in case of an exception the out parameter will never be completed.

Specter answered 30/9, 2019 at 9:42 Comment(1)
This is a very nice solution for some cases.Espalier
S
5

Pattern matching to the rescue! C#9 (I think) onwards:

// example of a method that would traditionally would use an out parameter
public async Task<(bool success, int? value)> TryGetAsync()
{
    int? value = // get it from somewhere
    
    return (value.HasValue, value);
}

Use it like this:

if (await TryGetAsync() is (true, int value))
{
    Console.WriteLine($"This is the value: {value}");
}
Snapback answered 26/1, 2023 at 10:29 Comment(0)
S
2

I think using ValueTuples like this can work. You have to add the ValueTuple NuGet package first though:

public async void Method1()
{
    (int op, int result) tuple = await GetDataTaskAsync();
    int op = tuple.op;
    int result = tuple.result;
}

public async Task<(int op, int result)> GetDataTaskAsync()
{
    int x = 5;
    int y = 10;
    return (op: x, result: y):
}
Sol answered 23/5, 2018 at 21:13 Comment(2)
You don’t need the NuGet if using .net-4.7 or netstandard-2.0.Gagliano
Hey, you're right! I just uninstalled that NuGet package and it still works. Thanks!Sol
S
2

This is very similar to the answer provided by Michael Gehling, but I had my own solution until I found his and noticed that I wasn't the first to think of using an implicit conversion.

Regardless, I wanted to share as mine also supports when nullable is set to enable

public readonly struct TryResult<TOut>
{
    #region constructors

    public TryResult(bool success, TOut? value) => (Success, Value) = (success, value);

    #endregion

    #region properties

    public                                            bool  Success { get; init; }
    [MemberNotNullWhen(true, nameof(Success))] public TOut? Value   { get; init; }

    #endregion

    #region methods

    public static implicit operator bool(TryResult<TOut> result) => result.Success;
    public static implicit operator TryResult<TOut>(TOut value) => new (true, value);

    public void Deconstruct(out bool success, out TOut? value) => (success, value) = (Success, Value);

    public TryResult<TOut> Out([NotNullWhen(true)] out TOut? value)
    {
        value = Value;

        return this;
    }

    #endregion
}

Then you can write a Try method like this:

public static async Task<TryResult<byte[]>> TryGetBytesAsync(string file) =>
    File.Exists(file)
        ? await File.ReadAllBytesAsync(file)
        : default(TryResult<byte[]>);

And call it like this:

if ((await TryGetBytesAsync(file)).Out(out var bytes))
    Console.WriteLine($"File has {bytes.Length} bytes.");
Selima answered 18/10, 2022 at 17:44 Comment(0)
M
1

My two cents. It is similar to the tuple examples, just with a generic interface and a class implementation.

public interface ITryGetResult<T>
{
  bool WasSuccessful { get; }
  T Value { get; }
}

public class TryGetResultModel<T> : ITryGetResult<T>
{
  public bool WasSuccessful { get; private set; }
  public T Value { get; private set; }

  public static TryGetResultModel<T> Success(T result)
  {
    return new TryGetResultModel<T>()
    {
      WasSuccessful = true,
      Value = result
    };
  }

  // Is diffrent for each TryGetResultModel<T>
  // https://mcmap.net/q/67908/-resharper-warns-quot-static-field-in-generic-type-quot
  public static TryGetResultModel<T> Failed = new TryGetResultModel<T>();
}

Usage example

public async Task<ITryGetResult<string>> TryGetAsync()
{
  result = await GetAsync();

  return result == default
    ? TryGetResultModel<string>.Failed
    : TryGetResultModel<string>.Success(result);
}

public async Task Main()
{
  var result = await TryGetAsync();

  if (result.WasSuccessful)
  {
    DoSomething(result.Value);
  }
}
Manolete answered 16/11, 2023 at 14:38 Comment(0)
B
0

For developers who REALLY want to keep it in parameter, here might be another workaround.

Change the parameter to an array or List to wrap the actual value up. Remember to initialize the list before sending into the method. After returned, be sure to check value existence before consuming it. Code with caution.

Bedspread answered 11/1, 2021 at 6:41 Comment(0)
A
-7

You can do this by using TPL (task parallel library) instead of direct using await keyword.

private bool CheckInCategory(int? id, out Category category)
    {
        if (id == null || id == 0)
            category = null;
        else
            category = Task.Run(async () => await _context.Categories.FindAsync(id ?? 0)).Result;

        return category != null;
    }

if(!CheckInCategory(int? id, out var category)) return error
Andros answered 26/11, 2019 at 11:35 Comment(2)
Never use .Result. It's an anti-pattern. Thanks!Mil
This method is not asynchronous. It does not answer the question.Bates

© 2022 - 2024 — McMap. All rights reserved.