【问题标题】:Convert any given function into an awaitable task将任何给定函数转换为等待任务
【发布时间】:2015-11-09 19:41:04
【问题描述】:

以下代码的目标是将任何给定函数转换为可等待函数。这个想法是在从数据库中获取数据时使用它,让代码可以灵活地使用同步获取函数(我当前的 ORM 的强制执行),或者使用与异步相同的函数。

我知道代码背后的概念可能有很多问题。到目前为止,我只是想摆脱编译器错误,这样我就可以运行代码并检查行为。但是,我当然愿意事先讨论这个概念,如果它背后的整个想法是错误的,那么可以更有效地利用我的时间寻找另一个解决方案。

async static void Main()
{
    // The following line gives a compiler error:
    // Error    1   The best overloaded method match for 'CastFuncToTask<int>(System.Func<int>)' has some invalid arguments 
    int task = await CastFuncToTask<int>(TestFunc(2));
}

private static Task<T> CastFuncToTask<T>(Func<T> func)
{
    TaskCompletionSource<T> taskCompletionSource = new TaskCompletionSource<T>();
    T result = func.Invoke();
    taskCompletionSource.SetResult(result);
    return taskCompletionSource.Task;
}

private static int TestFunc(int testInt) 
{
    return testInt * 2;
}

【问题讨论】:

  • FWIW 在.Net4.5及以上你的CastFuncToTask可以简单写成return Task.FromResult(func());
  • 这种方法比标准var result = await Task.Run(() =&gt; TestFunc(2)); 更好吗?
  • @Sinatr 是的。 Task.Run 当你知道你当前的线程只是要等待时,不必要地启动新的/现有的线程来工作。让你当前的线程工作,不要打扰线程池 :)
  • @SriramSakthivel 好的,所以基本上我正在尝试重新发明轮子,return Task.FromResult(func()); 正是我想要的。好的。 Task.Run“新线程”问题是我试图准确避免的问题,我的印象是,在搜索“如何使自定义函数等待”时,这是“最明显”的答案。我很高兴知道有一个内置函数可以满足我的需要。
  • @SriramSakthivel, “不必要” - 如果没有在单独的线程中启动同步方法,await 没有意义,它仍然同步调用方法(阻塞调用者线程,例如 UI 线程),所以我不是真的明白await Task.FromResult()的意思。这对我来说没有意义。我知道它可以用在假设返回 Task&lt;bool&gt; 的方法中,你可以做一些检查 if(someconditionfail) return Task.FromResult(false); 而不是尝试调用异步方法。

标签: c# async-await task-parallel-library


【解决方案1】:

运行 .NET 4.5,您可以通过以下方式大大简化您的代码:

int task = await Task.FromResult(TestFunc(2));

无需自己将其包装在 TaskCompletionSource 中。

我知道这个概念可能有很多问题 代码后面。

如果您尝试异步查询数据库,那么此解决方案肯定无济于事。它只是人为地将您的结果包装在Task 中。如果你真的想异步查询你的数据库,你需要使用你的数据库提供者提供的异步方法。

如果您正在使用 MySQL 并寻找支持异步的驱动程序,请查看 Dapper

【讨论】:

  • 我怀疑这会奏效。 TestFunc 将在将结果传递给Task.FromResult 之前进行评估,我认为 T.Rahgooy 的答案更好。这仅适用于无参数函数:Task.FromResult(TestFunc)
  • @SchlaWiener 这正是他的方法正在做的事情。他正在调用该方法并将其包装在 Task 中。
  • @Eser 那是我原来的答案。它被错误地编辑了:)
  • @SchlaWiener 两个用户 YuvalItzchakov(在这个答案中)和 SriramSakthivel(在问题的 cmets 中)都指出我正在重新发明轮子,但现在你说这个内置的解决方案可能不适合我的需要。除了自负,我想知道我最好的选择是什么。
  • @XavierPeña 将您的代码放入 WinForms 应用程序并查看结果(还可以向 TestFunction 添加一些长时间运行的代码)。这两种解决方案都会阻塞你的 UI 线程......
【解决方案2】:

你可以使用:

await Task.Run(() => obj.functionname());

【讨论】:

    【解决方案3】:

    改成

    int task = await CastFuncToTask<int>(()=>TestFunc(2));
    

    在您的代码中,为CastFuncToTask 提供的输入是intTestFunc 返回的内容。)但它需要一个委托Func&lt;T&gt;

    【讨论】:

      【解决方案4】:

      我的方法是这样的:

      public Task<int> SetOffAsync()
      {
          return Task<int>.Factory.StartNew(() =>
          { 
              /*do something else*/
              return 42;
          });
      }
      

      你可以这样称呼:

      int var = await SetOffAsync();
      

      【讨论】:

      • 正如 SchlaWiener 在另一个答案中指出的那样:“Task.Run 只是 Task.StartNew 的快捷方式(这使得创建新任务更加明显。”正如我所说: “Task.Run 打开一个新线程,因此没有充分利用异步等待......”。
      • 我同时在写这个
      【解决方案5】:

      使用 Windows 窗体应用程序,我确认第一个调用阻止了第二个调用。所以最好的解决方案是使用 Task.Run() 和 lambda 表达式。

          private async void button1_Click(object sender, EventArgs e)
          {
              button1.Text = "...";
              var result = await Task.FromResult(TestFunc(2));
              button1.Text = result.ToString();
          }
      
          private async void button2_Click(object sender, EventArgs e)
          {
              button2.Text = "...";
              var result = await Task.Run(() => TestFunc(2));
              button2.Text = result.ToString();
          }
      
          private int TestFunc(int x)
          {
              System.Threading.Thread.Sleep(5000);
              return x;
          }
      

      编译器无法知道您不想评估 TestFunc(2) 而是将其用作委托,并且只会首先执行该方法,并且 Task.FromResult 只会将 return value 包装在一个任务中。这不是你想要的。

      【讨论】:

      • Task.Run 包装一个同步方法来做IO 绑定的工作很少是一个好主意。
      • 我同意Task.FromResult似乎同步评估函数。但是Task.Run 打开了一个新线程,因此没有充分利用异步等待......也许这个问题没有“好的”解决方案。
      • @XavierPeña 有,使用异步 API。您使用的是哪个数据库?
      • 我认为Task.Run() 就是为这种情况而设计的。是的,您有开销,但对于许多等待/异步场景来说都是如此。 Task.Run 只是Task.StartNew 的快捷方式(这使得创建新任务更加明显。msdn 上有一篇很好的文章:blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
      • @YuvalItzchakov 嗯,是的,这是真的……通过 NPoco 使用 MySQL。 NPoco 有异步获取,但不允许在这种特殊情况下使用 linq(奇怪的是,它在使用同步获取时会这样做)。找到一个可靠的快速 ORM 有点噩梦……再次切换会有点麻烦。此外,我最初的问题是故意通用的,因为在我的代码中可能存在这种“等待转换”的其他情况。
      【解决方案6】:

      以下代码可能对其他人有所帮助:

      注意:GenericMethod 是我们作为 Func 传递的参数名称,输入 (T1) 和输出 (T2) 为泛型类型。 任务工厂将提供运行任务所需的访问权限。

          public async Task<T2> GenericAsyncCall<T1, T2>(Func<T1, T2> GenericMethod, T1 input)
          {
              var _output = await Task.Factory.StartNew(() => {
                  var output = GenericMethod.Invoke(input);
                  return output;
              });
      
              return (T2)_output;
          }
      

      【讨论】:

        【解决方案7】:

        问题在于 CastFuncToTask 同步运行。 如果 Func 是无参数的,你可以这样做:

        public static Task&lt;T&gt; ToTask(this Func&lt;T&gt; func) =&gt; Task.Factory.StartNew(() =&gt; func());

        无论如何,我们都可以使用 TaskCompletionSource 并返回我们想要的东西,而不管方法如何。假设我们有 Action 而不是 Func

        public static Task<string> ToTask(this Action action)    
        {    TaskCompletionSource<string> taskCompletionSource = new 
                   TaskCompletionSource<string>();
                   string result = null;
                   try
                   {
                       Task.Factory.StartNew(() => action());
                   }
                   catch (Exception ex)
                   {
                       result = ex.Message;
                   }
                   taskCompletionSource.SetResult(result);
                   return taskCompletionSource.Task;
               }
        
        猜你喜欢
        • 2017-06-13
        • 2015-02-02
        • 2014-03-29
        • 2013-09-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多