Как дождаться получения обратного вызова от асинхронного метода (C#) - PullRequest
0 голосов
/ 03 августа 2020

У меня есть метод, который извлекает некоторые значения из Firebase, как показано ниже:

 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);
            }
        });
     }

Я вызываю этот метод из другого метода, например:

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

Ожидаемый результат:

  1. Я выполняюсь, когда готов
  2. Готово, получение значений
  3. Значение: testValue
  4. Завершенное выполнение

Какой результат Я получаю:

  1. Я выполняю, когда закончил
  2. Значение: (null)
  3. Завершенное выполнение
  4. Получение значений завершено

Я попытался назначить valueReturned в лямбда-выражении метода GetTable. Но это дало ошибку: «Невозможно указать параметр пользователя в лямбда-выражении»

Ответы [ 3 ]

3 голосов
/ 03 августа 2020

Рассмотрите возможность возврата Task из getTable и await в 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;
}

В противном случае ваш getTable метод запускает Task и продолжает выполнение, поэтому GetTable присваивает null valueReturned (при условии, что CallAsync работает достаточно долго) и продолжается. Также я бы сказал, что вы можете просто вернуть Task<IDictionary> из getTable.

Если по какой-то причине вы не можете сделать свой код асинхронным, есть несколько вариантов для синхронного запуска метода asyn c , в зависимости от вашего контекста некоторые могут быть более подходящими, чем другие, но в любом случае вам следует подходить к этому с осторожностью.

1 голос
/ 03 августа 2020

Если мы сократим ваш частный метод до ядра, это будет не более чем это:

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

Только asyn не может иметь ref в параметрах. Вы должны либо вернуть Task, либо сделать вызов syn c:

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

Как сказал @Miral: «этот дизайн неправильный». С точки зрения asyn / await мы могли бы преобразовать обе функции в один простой метод asyn c:

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"];
}
1 голос
/ 03 августа 2020

Более идиоматический c способ записи getTable будет выглядеть следующим образом:

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

(Действительно ли это хорошая идея - другой вопрос, так как вы жертвуете информацией об ошибках и конвертируете asyn c метод в обратном вызове обычно немного бессмысленен ...)

Но ваш метод GetTable просто обречен на провал. Вы никогда не сможете вернуть результат вызова asyn c в выходном параметре, потому что внешняя функция уже завершилась до завершения внутреннего вызова. Единственный способ, которым это могло бы сработать, - это если GetTable выполняет блокирующее ожидание на getTable, которое затем уничтожает весь смысл использования asyn c в первую очередь.

Единственный реальный ответ заключается в том, что ваш текущий дизайн неправильный, и вам нужно сделать это по-другому. Asyn c полностью вверх - это единственный способ получить go.

...