简体   繁体   中英

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"

Consider returning Task from getTable and await ing 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.

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.

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"];
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM