【问题标题】:How to wait until a Callback is received from an asynchronous method (C#)如何等到从异步方法收到回调(C#)
【发布时间】:2020-08-02 22:04:57
【问题描述】:

我有一个从 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. 完成获取值

我尝试在 GetTable 方法的 lambda 表达式中分配“valueReturned”。但它给出了错误:“无法在 lambda 表达式中引用用户引用参数”

【问题讨论】:

  • 您的function.CallAsync(data).ContinueWith 没有等待,这就是为什么您在“完成获取价值”之前获得“完成执行”的原因。正如@Miral 提到的:将异步方法转换为回调是没有意义的。

标签: c# unity3d asynchronous task


【解决方案1】:

考虑从getTable 和await返回Task 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 运行时间足够长)并继续执行。另外我想说你可以从getTable返回Task&lt;IDictionary&gt;。

如果由于某种原因您不能使代码异步,run async method synchronously 有多个选项,取决于您的上下文,有些选项可能比其他选项更合适,但无论哪种方式,您都应谨慎处理。

【讨论】:

    【解决方案2】:

    写getTable 的更惯用方式是:

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

    (这实际上是否是一个好主意是一个不同的问题,因为您牺牲了错误信息并将异步方法转换为回调通常有点毫无意义......)

    但是您的GetTable 方法注定要失败。您永远不能在输出参数中返回异步调用的结果,因为在内部调用完成之前外部函数已经退出。这可能起作用的唯一方法是GetTable 在getTable 上进行阻塞等待,然后首先破坏使用异步的全部意义。

    唯一真正的答案是您当前的设计是错误的,您需要以不同的方式执行此操作。一直异步是唯一的方法。

    【讨论】:

      【解决方案3】:

      如果我们将您的私有方法剥离到核心,那就是:

      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 或使通话同步:

      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 所说:“那个设计是错误的”。从 async/await 的角度来看,我们可以将这两个函数重构为一个简单的异步方法:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-08-19
        • 2013-02-14
        • 1970-01-01
        • 1970-01-01
        • 2013-07-24
        • 2017-03-06
        • 2018-11-14
        • 2011-05-10
        相关资源
        最近更新 更多