【问题标题】:How to construct a Task without starting it?如何在不启动任务的情况下构建任务?
【发布时间】:2013-04-10 14:06:28
【问题描述】:

我想使用this Task<TResult> 构造函数。我似乎无法正确使用语法。有人可以更正我的代码吗?

另外,我认为如果Task 是这样构造的,它不会启动吗?

我认为我需要的构造函数是:

Task<TResult>(Func<Object, TResult>, Object)

我得到的错误是:

参数 1:无法从“方法组”转换为“System.Func&lt;object,int&gt;

static void Main(string[] args)
{
    var t = new Task<int>(GetIntAsync, "3"); // error is on this line
    // ...
}

static async Task<int> GetIntAsync(string callerThreadId)
{
    // ...
    return someInt;
}

【问题讨论】:

  • 我一头雾水,你的方法已经是async,为什么还要把它封装在另一个Task中?
  • :-) 为您在 this 线程上实际评论过的内容建模
  • 好吧,这样你会得到一个未启动的Task,但它与async方法返回的Task不同。
  • 这是一个围绕它的任务?
  • 是的,它是一个基于代码的Task,它会在启动时调用promise Task

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


【解决方案1】:

有一个类似的问题,我们希望确保任务可重用,而无需重新启动任务。 Rx 救了我们的培根。您将需要 nuget 包 System.Reactive

见下文GetIntTask 返回一个未启动的任务,GetIntOnce 确保任务启动,并且任务的结果被缓冲并重播给任何调用者(使用Replay(1)

async Task Main()
{
    var awaitableResult = GetIntOnce();
    Console.WriteLine("Has the task started yet? No!");
    var firstResult = await awaitableResult;
    Console.WriteLine($"Awaited Once: {firstResult}");
    var secondResult = await awaitableResult;
    Console.WriteLine($"Awaited Twice: {secondResult}");
}

public static IObservable<int> GetIntOnce()
{
    return Observable.FromAsync(() =>
        {
            var t = GetIntTask();
            t.Start();
            return t;
        }).Replay(1).AutoConnect();
}

public static Task<int> GetIntTask()
{
    var t = new Task<int>(
    () =>
    {
        Console.WriteLine("Task is running!");
        return 10;
    });
    return t;
}

输出是:

Has the task started yet? No!
Task is running!
Awaited Once: 10
Awaited Twice: 10

【讨论】:

  • 以上作者是我的同事,他通过 Rx 帮助我解决了这个问题。经过一番痛苦,我意识到Task的基本设计是错误的。
  • 虽然这行得通,但在我看来这是一种相当复杂的做事方式。使用这种方法,您还会失去Task 类的功能,例如IsCompletedIsFaultedStatus 属性,同时等待多个操作await Task.WhenAll() 的能力等等。我建议看看这个问题:Enforce an async method to be called once
【解决方案2】:

.net 5 中的一种新方法(其实我不知道你什么时候可以开始这样做,我现在正在使用 .net 5)

Task<int> MyStartLaterTask = null;
//... Some Time Later
MyStartLaterTask ??= GetIntAsync(string callerThreadId);
await MyStartLaterTask;

我很欣赏这并不完全是“预先定义并稍后使用”,但据我所知,这也是同样的事情。我有一个循环,我想定义一个只在需要时运行并且只运行一次的任务。这就是这样做的,它创建了一个任务,我可以在第一次需要它时分配它,然后在需要它的输出时等待它。

【讨论】:

    【解决方案3】:

    我发现异步 lambda 效果最好,因为:

    1. 工作是通过调用方法开始的,这意味着你不需要调用.Start,所以不可能忘记这样做。
    2. 这种方法不会“遭受”内部/外部任务问题;你总是在等待正确的任务,而不是它的包装。
    Func<Task<int>> f = async () => await WorkAsync(2);
    
    Console.WriteLine("Sleeping before start");
    
    await Task.Delay(100);
    
    Console.WriteLine("Waking up to start");
    
    var result = await f();
    
    Console.WriteLine($"The work is done and the answer is: {result}");
    

    这会产生以下输出:

    Sleeping before start
    Waking up to start
    Starting 2
    Ending 2
    The work is done and the answer is: 4
    
    

    【讨论】:

    • 您是不是被无权访问该任务的问题困扰?你只能访问它的结果,但你不能查询 Task.IsFaulted、Task.Exception 等的值。当那个愚蠢的 lambda 函数被调用时,任务就开始了。我想使用 lambda 函数实际创建任务,然后通过 await 启动它,这样我就可以访问任务 =) 令人沮丧:D
    • @Daveloper 我不确定我是否遵循。 Func&lt;Task&gt; 不能实现什么,因为 1. 可以在您需要时启动; 2.f() 给出一个句柄 3. 你可以await 或传递它WhenAny 等等。 4. 你可以检查它的状态 5. 错误处理可以在lamba 或启动任务的代码中完成。
    【解决方案4】:

    要使用接受object state 参数的Task 构造函数,您还必须有一个接受object 参数的函数。一般这是不方便的。存在此构造函数的原因是为了避免在热路径中分配对象(closure)。对于正常使用,闭包的开销可以忽略不计,避免它们会使您的代码无缘无故地复杂化。所以this 是你应该使用的构造函数:

    public Task (Func<TResult> function);
    

    ...用这个 lambda 作为参数:

    () => GetIntAsync("3")
    

    不过,您的情况有一个特点:您传递给构造函数的 lambda 返回一个 Task&lt;int&gt;。这意味着泛型类型TResult 被解析为Task&lt;int&gt;,所以你最终得到了一个嵌套任务:

    var t = new Task<Task<int>>(() => GetIntAsync("3"));
    

    启动外部任务将导致创建内部任务。要获得最终结果,您必须使用两次await 运算符,一次用于完成外部任务,一次用于完成内部任务:

    static async Task Main(string[] args)
    {
        var outerTask = new Task<Task<int>>(() => GetIntAsync("3"));
        //...
        outerTask.Start(); // or outerTask.RunSynchronously() to use the current thread
        //...
        Task<int> innerTask = await outerTask; // At this point the inner task has been created
        int result = await innerTask; // At this point the inner task has been completed
    }
    

    【讨论】:

      【解决方案5】:
      var t = new Task<int>(() => GetIntAsync("3").Result);
      

      或者

      var t = new Task<int>((ob) => GetIntAsync((string) ob).Result, "3");
      

      为避免使用 lambda,您需要编写如下静态方法:

      private static int GetInt(object state)
      {
         return GetIntAsync(((string) state)).Result;
      }
      

      然后:

      var t = new Task<int>(GetInt, "3");
      

      【讨论】:

      • 在第二种情况下,您仍在关闭this,因此您只使用状态来传递两个变量之一。
      • 你说得对,关闭存在,但 this 没有被使用,因为 GetIntAsync 方法是静态的。
      • 不是害虫,但你能展示不使用 lambdas 的语法
      • 不确定这与Q如何对应,使用Task.Result会阻塞,这似乎不对,这与创建但不启动Task有什么关系?你能不使用异步 lambda 然后等待结果吗?
      • 这种方法是使用异步来同步转换,这是危险的(死锁风险等)。因此,Theodor 的方法更好。
      【解决方案6】:
         //creating task
         var yourTask = Task<int>.Factory.StartNew(() => GetIntAsync("3").Result);
      
         //...
      
         int result = yourTask.Result;
      

      更新:

      是的,不幸的是它确实启动了任务。改用上面提到的代码:

         //creating task
         var yourTask = new Task<int>(() => GetIntAsync("3").Result);
      
         //...
      
         // call task when you want
         int result = yourTask.Start();
      

      【讨论】:

      • 这是创建并开始。任务。他想创建它而不启动它。
      猜你喜欢
      • 1970-01-01
      • 2012-07-05
      • 1970-01-01
      • 2017-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-27
      相关资源
      最近更新 更多