Faizan Khan
Faizan Khan

Reputation: 207

How to wait until a Callback is received from an asynchronous method (C#)

I have a method which fetches some values from Firebase as below:

 void  getTable(Action<IDictionary> callBack)
    {
         // function & data initialized here
        function.CallAsync(data).ContinueWith((callTask) =>
        {
            if (callTask.IsFaulted)
               callBack(null);
            else if (callTask.IsCompleted)
            {    
                Debug.Log("I am being executed when done");
                var result = (IDictionary) callTask.Result.Data;
                callBack(result);
            }
        });
     }

I call this method from another method as like:

 public void GetTable(ref string valueReturned)
    {
        string val = null;
        getTable((dictionary =>
        {
            Debug.Log("Done getting values");
            val = (string) dictionary["someValue"]; // returns "testValue" to val
        }));

        valueReturned = val;
        Debug.Log("Value is:" + valueReturned);
        Debug.Log("Completed Execution");
    }

Expected Output:

  1. I am being executed when done
  2. Done getting values
  3. Value is: testValue
  4. Completed Execution

What output I am getting is:

  1. I am being executed when done
  2. Value is: (null)
  3. Completed Execution
  4. Done getting values

I tried to assign "valueReturned" within the lambda expression of GetTable method. But it gave error : "Cannot user ref parametre inside a lambda expression"

Upvotes: 0

Views: 1931

Answers (3)

Arvis
Arvis

Reputation: 8363

If we strip down your private method to the core it's nothing more than this:

private async Task<IDictionary> getTable()
{
    // function & data initialized here
    var result = await function.CallAsync(data);
    return result?.Data;
}

public async Task<string> GetTable()
{
    var dictionary = await getTable();
    var valueReturned = (string)dictionary["someValue"]; // returns "testValue" to val
    Debug.Log("Value is:" + valueReturned);
    Debug.Log("Completed Execution");
    return valueReturned;
}

Only asyn can't have ref in parametrs. You must either return Task or make call sync:

public void GetTable(ref string valueReturned)
{
    var dictionary = getTable().Result;
    valueReturned = (string)dictionary["someValue"]; // returns "testValue" to val
    Debug.Log("Value is:" + valueReturned);
    Debug.Log("Completed Execution");
}

As @Miral said: "that design is wrong". From the asyn/await point of view we could refactor both functions to one simply async methode:

public async Task<string> GetTableAsync()
{
    // <function & data initialized here>
    var result = await function.CallAsync(data);
    var dictionary = result?.Data;
    return dictionary == null ? null: (string)dictionary["someValue"];
}

Upvotes: 1

Guru Stron
Guru Stron

Reputation: 143233

Consider returning Task from getTable and awaiting it in GetTable:

Task getTable(Action<IDictionary> callBack)
{
     // function & data initialized here
    return function.CallAsync(data).ContinueWith((callTask) =>
    {
        if (callTask.IsFaulted)
           callBack(null);
        else if (callTask.IsCompleted)
        {    
            Debug.Log("I am being executed when done");
            var result = (IDictionary) callTask.Result.Data;
            callBack(result);
        }
    });
 }

public Task<string> GetTable()
{
    string val = await getTable((dictionary =>
    {
        Debug.Log("Done getting values");
        val = (string) dictionary["someValue"]; // returns "testValue" to val
    }));

    Debug.Log("Value is:" + val);
    Debug.Log("Completed Execution");
    return val;
}

Otherwise your getTable method starts a Task and continues execution, so GetTable assigns null to valueReturned (assuming that CallAsync runs long enough) and proceeds. Also I would say that you can just return Task<IDictionary> from getTable.

If for some reason you can't make your code asynchronous there are multiple options to run async method synchronously, dependent on your context some can be more appropriate than others, but either way you should approach this with caution.

Upvotes: 1

Miral
Miral

Reputation: 13095

A more idiomatic way to write getTable would be:

async Task getTable(Action<IDictionary> callBack)
{
    IDictionary result;
    try
    {
        result = await function.CallAsync(data);
    }
    catch
    {
        result = null;
    }
    callback(result);
}

(Whether this is actually a good idea is a different question, since you're sacrificing error information and converting an async method into a callback is usually a bit pointless...)

But your GetTable method is simply doomed to failure. You can never return the result of an async call in an output parameter, because the outer function has already exited before the inner call completes. The only way this could possibly work is if GetTable does a blocking wait on getTable, which then destroys the whole point of using async in the first place.

The only real answer is that your current design is wrong, and you need to do this differently. Async all the way up is the only way to go.

Upvotes: 1

Related Questions