【问题标题】:Is there any way to start task using ContinueWith task?有没有办法使用 ContinueWith 任务启动任务?
【发布时间】:2011-05-23 11:46:10
【问题描述】:

我的代码:

var r = from x in new Task<int>(() => 1)
        from y in new Task<int>(() => x + 1) 
        select y;
r.ContinueWith(x => Console.WriteLine(x.Result)).Start();   

new Task<int>(() => 1)
    .ContinueWith(x => x.Result + 1)
    .ContinueWith(x => Console.WriteLine(x.Result))
    .Start();

例外:

不能在继续任务上调用开始。

所以我需要开始第一个任务。有没有办法调用last task的Start方法来运行所有的任务?

【问题讨论】:

  • 我仍然认为这个问题没有答案,真的。我也在研究这个问题的解决方案。为了强调你写的东西:我只想编写任务。关于是否运行它的决定可以在任何地方

标签: c# .net exception-handling task-parallel-library


【解决方案1】:

答案很简单。 ContinueWith 是自动启动任务。第一个任务需要运行。

var r= Task<int>.Run<int>( () => 1 )
             .ContinueWith<int>( x => x.Result + 1 )
             .ContinueWith( x => Console.WriteLine( x.Result ) );

ContinueWith 返回从检查前一个任务开始的任务是否完成。此代码的工作方式与下面的代码相同

 var firstTask = new Task<int>( () => 1 );
        firstTask.Start();
        var firstawaiter = firstTask.GetAwaiter();
        var secondTask = new Task<int>( x => (int)x + 1 , firstawaiter.GetResult());

        firstawaiter.OnCompleted( () =>
        {
            secondTask.Start();
        } );

        var secondawaiter = secondTask.GetAwaiter();


        var thirdTask = new Task( x => Console.WriteLine( x ) , secondawaiter.GetResult());

        secondawaiter.OnCompleted( () =>
        {
            thirdTask.Start();
        } );

所以,如果第一个任务没有完成,下一个任务将不会开始。

而且你不需要开始 continuewith block 。

【讨论】:

    【解决方案2】:

    有什么理由不使用Task.Factory.StartNewmsdnms docs 来完成第一个任务?是的,它是不一致的 - 但它从根本上是一种不同的任务,就明确地开始而不是仅仅作为延续而言。

    【讨论】:

    • 我只想编写任务。关于是否运行它的决定可以在任何地方。
    • @dotneter:另一个选择是在查询表达式之外创建初始任务,然后在查询表达式中使用它,然后启动它。
    • 但是如果我想从方法中返回我的复杂任务,我需要处理两个任务,root 用于启动,最后一个用于组合 ContinueWith
    • @dotneter:所以你想要一个可以开始的任务,但是查询结束的结果?不确定最好的方法,但我想我明白你的意思......
    • 是的,我认为它类似于标准 linq 场景。
    【解决方案3】:

    据我所知,没有明智的方法来组合框架提供的未启动任务。我能想到的最简单的解决方案是扩展方法。如果您需要此功能,可以在此基础上构建一些示例。

    警告:就像传递和编写大量 lambdas 一样,如果您发现自己需要这些,这通常意味着您在设计中缺少一种可以简化代码的类型。问问自己通过创建子任务获得了什么。

    /// <summary>
    /// Compose tasks without starting them.
    /// Waiting on the returned task waits for both components to complete.
    /// An exception in the first task will stop the second task running.
    /// </summary>
    public static class TaskExtensions
    {
        public static Task FollowedBy(this Task first, Task second)
        {
            return FollowedBy(first,
                () =>
                {
                    second.Start();
                    second.Wait();
                });
        }
    
        public static Task FollowedBy(this Task first, Action second)
        {
            return new Task(
                () =>
                {
                    if (first.Status == TaskStatus.Created) first.Start();
                    first.Wait();
                    second();
                });
        }
    
        public static Task FollowedBy<T>(this Task first, Task<T> second)
        {
            return new Task<T>(
                () =>
                {
                    if (first.Status == TaskStatus.Created) first.Start();
                    first.Wait();
                    second.Start();
                    return second.Result;
                });
        }
    
        public static Task FollowedBy<T>(this Task<T> first, Action<T> second)
        {
            return new Task(
                () =>
                {
                    if (first.Status == TaskStatus.Created) first.Start();
                    var firstResult = first.Result;
                    second(firstResult);
                });
        }
    
        public static Task<TSecond> FollowedBy<TFirst, TSecond>(this Task<TFirst> first, Func<TFirst, TSecond> second)
        {
            return new Task<TSecond>(
                () =>
                {
                    if (first.Status == TaskStatus.Created) first.Start();
                    return second(first.Result);
                });
        }
    }
    

    【讨论】:

      【解决方案4】:

      我今天也遇到了同样的问题。我想创建一个处理来自内部任务的错误的包装器任务。这是我想出的:

      var yourInitialTask = new Task(delegate
      {
          throw e;
      });
      
      var continuation = task.ContinueWith(t =>
      {
          if (task.IsCanceled)
          {
              Debug.WriteLine("IsCanceled: " + job.GetType());
          }
          else if (task.IsFaulted)
          {
              Debug.WriteLine("IsFaulted: " + job.GetType());
          }
          else if (task.IsCompleted)
          {
              Debug.WriteLine("IsCompleted: " + job.GetType());
          }
      }, TaskContinuationOptions.ExecuteSynchronously); //or consider removing execute synchronously if your continuation task is going to take long
      
      var wrapper = new Task(() =>
      {
         task.Start();
         continuation.Wait();
      });
      
      return wrapper;
      

      这里的主要特点是 - 延续部分在原始任务之后运行,如您所愿 - 包装器是可启动的。使用ContineWith() 创建的后续任务不可启动。

      这个例子的一个不太重要的特性是异常被记录和丢弃(解决了我的问题,而不是你的问题)。当 continuation 中发生异常时,您可能想要做一些不同的事情,例如将其作为当前任务的异常重新抛出,以便它冒泡。

      【讨论】:

        【解决方案5】:

        问题在于使用 LINQ 选择任务只会创建一个表达式树!

        所以这是你需要做的:

        var query = 
            from i in Enumerable.Range(1, 4) 
            let task = Task.Factory.StartNew(() => Tuple.Create(i, IsPrime(i))) // put a breakpoint here
            select task.ContinueWith(delegate {
                Console.WriteLine("{0} {1} prime.", _.Result.Item1, _.Result.Item2 ? "is" : "is not");
            });
        // breakpoint never hit yet
        query.ToArray(); // breakpoint hit here 4 times
        // all tasks are now running and continuations will start
        TaskEx.Await(query.ToArray()); // breakpoint hit 4 more times!!
        

        【讨论】:

          【解决方案6】:

          我不确定写这个有什么问题:

          var t1 = new Task<int>(() => 1)
          var r = from x in t1
                  from y in new Task<int>(() => x + 1) 
                  select y;
          r.ContinueWith(x => Console.WriteLine(x.Result));
          t1.Start();
          

          或者这个:

          var t = new Task<int>(() => 1)
          t.ContinueWith(x => x.Result + 1)
           .ContinueWith(x => Console.WriteLine(x.Result))
          t.Start();
          

          这直接表达了你真正想做的事情。 (这是您要启动的初始任务。那么在该初始任务上调用 Start 有什么问题?)您为什么要寻找一种掩盖它的语法?

          编辑:修复第一个示例...

          编辑 2 以添加:

          所以我现在意识到 LinqToTasks 期望任务选择器返回正在运行的任务。因此,您的第一个示例中的第二个 from 子句返回一个任务,该任务将永远不会运行。所以你真正需要的是这样的:

          var t1 = new Task<int>(() => 1);
          var r = from x in t1
                  from y in Task<int>.Factory.StartNew(() => x + 1)
                  select y;
          r.ContinueWith(x => Console.WriteLine(x.Result));
          t1.Start();
          

          在这些from 子句中产生的任务上没有其他东西会调用Start。由于相关选择器直到前一个任务完成后才会真正执行,因此您仍然可以控制何时启动根任务。

          这似乎可行,但它很丑陋。但这似乎是 LinqToTasks 的设计方式......我想我会坚持使用常规函数调用语法。

          【讨论】:

          • 第一个,抛出异常 Start 可能不会在具有 null 操作的任务上调用。第二个没问题,但是如果我想从方法中返回我的复杂任务,我需要处理两个任务,root 用于启动,最后一个用于组合 ContinueWith。
          • 是的,抱歉 - 我发布后立即意识到我的第一个示例是错误的,因此我对其进行了更新,将初始任务放在单独的变量中。
          猜你喜欢
          • 2021-04-16
          • 1970-01-01
          • 2018-02-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多