C# 如何使顺序处理像并行处理一样简单

C# 如何使顺序处理像并行处理一样简单,c#,asynchronous,.net-4.0,.net-4.5,system.reactive,C#,Asynchronous,.net 4.0,.net 4.5,System.reactive,我有两个.net任务对象,我可能希望并行或按顺序运行。在这两种情况下,我都不想阻止线程等待它们。事实证明,让这个平行的故事变得简单美丽。但是,当我尝试按顺序安排任务时,代码可以工作,但感觉很尴尬 我想知道是否有人能展示如何使顺序版本更简洁,或者像并行版本一样轻松地编码。没有必要使用反应式扩展来回答这个问题 以下是我针对并行和顺序处理的两种解决方案,仅供参考 并行处理版本 这是纯粹的快乐: public Task<string> DoWorkInParallel() {

我有两个.net任务对象,我可能希望并行或按顺序运行。在这两种情况下,我都不想阻止线程等待它们。事实证明,让这个平行的故事变得简单美丽。但是,当我尝试按顺序安排任务时,代码可以工作,但感觉很尴尬

我想知道是否有人能展示如何使顺序版本更简洁,或者像并行版本一样轻松地编码。没有必要使用反应式扩展来回答这个问题

以下是我针对并行和顺序处理的两种解决方案,仅供参考

并行处理版本 这是纯粹的快乐:

    public Task<string> DoWorkInParallel()
    {
        var result = new TaskCompletionSource<string>();

        Task<int> AlphaTask = Task.Factory.StartNew(() => 4);
        Task<bool> BravoTask = Task.Factory.StartNew(() => true);

        //Prepare for Rx, and set filters to allow 'Zip' to terminate early
        //in some cases.
        IObservable<int> AsyncAlpha = AlphaTask.ToObservable().TakeWhile(x => x != 5);
        IObservable<bool> AsyncBravo = BravoTask.ToObservable().TakeWhile(y => y);

        Observable
            .Zip(
                AsyncAlpha,
                AsyncBravo,
                (x, y) => y.ToString() + x.ToString())
            .Timeout(TimeSpan.FromMilliseconds(200)).Subscribe(
                (x) => { result.TrySetResult(x); },
                (x) => { result.TrySetException(x.GetBaseException()); },
                () => { result.TrySetResult("Nothing"); });

        return result.Task;
    }
公共任务DoWorkInParallel()
{
var result=new TaskCompletionSource();
Task AlphaTask=Task.Factory.StartNew(()=>4);
Task BravoTask=Task.Factory.StartNew(()=>true);
//准备接收,并设置过滤器以允许“Zip”提前终止
//在某些情况下。
IObservable asynchAlpha=AlphaTask.ToObservable().TakeWhile(x=>x!=5);
IObservable asynchbravo=BravoTask.ToObservable().TakeWhile(y=>y);
可观察
Zip先生(
阿尔法,
好极了,
(x,y)=>y.ToString()+x.ToString())
.Timeout(TimeSpan.From毫秒(200)).Subscribe(
(x) =>{result.TrySetResult(x);},
(x) =>{result.TrySetException(x.GetBaseException());},
()=>{result.TrySetResult(“Nothing”);});
返回结果。任务;
}
顺序/管道处理版本 这是可行的,但很笨拙:

    public Task<string> DoWorkInSequence()
    {
        var result = new TaskCompletionSource<string>();

        Task<int> AlphaTask = Task.Factory.StartNew(() => 4);

        AlphaTask.ContinueWith(x =>
        {
            if (x.IsFaulted)
            {
                result.TrySetException(x.Exception.GetBaseException());
            }
            else
            {
                if (x.Result != 5)
                {
                    Task<bool> BravoTask = Task.Factory.StartNew(() => true);
                    BravoTask.ContinueWith(y =>
                    {
                        if (y.IsFaulted)
                        {
                            result.TrySetException(y.Exception.GetBaseException());
                        }
                        else
                        {
                            if (y.Result)
                            {
                                result.TrySetResult(x.Result.ToString() + y.Result.ToString());
                            }
                            else
                            {
                                result.TrySetResult("Nothing");
                            }
                        }
                    });
                }
                else
                {
                    result.TrySetResult("Nothing");
                }
            }
        }
        );

        return result.Task;
    }
public Task DoWorkInSequence()
{
var result=new TaskCompletionSource();
Task AlphaTask=Task.Factory.StartNew(()=>4);
AlphaTask.ContinueWith(x=>
{
如果(x.IsFaulted)
{
result.TrySetException(x.Exception.GetBaseException());
}
其他的
{
如果(x.Result!=5)
{
Task BravoTask=Task.Factory.StartNew(()=>true);
BravoTask.ContinueWith(y=>
{
如果(y.IsFaulted)
{
result.TrySetException(y.Exception.GetBaseException());
}
其他的
{
如果(y.结果)
{
result.TrySetResult(x.result.ToString()+y.result.ToString());
}
其他的
{
结果。TrySetResult(“无”);
}
}
});
}
其他的
{
结果。TrySetResult(“无”);
}
}
}
);
返回结果。任务;
}
在上面的顺序代码中,它变得一团糟,我甚至没有添加与并行版本相匹配的代码

要求(于6月8日更新) 对于回答问题的人,请注意:

  • 顺序场景应允许安排第一个任务的输出提供第二个任务的输入。我上面的示例“笨拙”代码可以很容易地实现这一点

  • 我对.NET4.5的答案感兴趣,但对我来说,.NET4.0的答案同样或更重要

  • 任务“Alpha”和“Bravo”的完成时间限制为200ms;它们不是每个都有200毫秒。在连续的情况下也是如此

  • 如果任一任务返回无效结果,则SourceCompletionTask必须在两个任务完成之前提前完成。无效结果为[AlphaTask:5]或[BravoTask:false],如示例代码中的显式测试所示。
    更新8/8:澄清-在顺序情况下,如果AlphaTask未成功或超时已经发生,则根本不应执行BravoTask

  • 假设AlphaTask和BravoTask都无法阻止。这并不重要,但在我的真实场景中,它们实际上是异步WCF服务调用

  • 也许我可以利用Rx的某个方面来清理顺序版本。但即使仅仅是任务编程本身也应该有一个更好的故事。我们拭目以待

    勘误表在两个代码示例中,我都将返回类型更改为Task,因为海报上的答案非常正确,我不应该返回TaskCompletionSource。

    公共任务DoWorkInSequence()
    
    public Task<string> DoWorkInSequence()
    {
        Task<int> AlphaTask = Task.Factory.StartNew(() => 4);
        Func<int> BravoFunc = x => 2 * x;
    
        //Prepare for Rx, and set filters to allow 'Zip' to terminate early
        //in some cases.
        IObservable<int> AsyncAlpha = AlphaTask.ToObservable().TakeWhile(x => x != 5);
    
        return AsyncAlpha
            .Do(x => Console.WriteLine(x))  //This is how you "Do WORK in sequence"
            .Select(BravoFunc)              //This is how you map results from Alpha
                                            //via a second method.
            .Timeout(TimeSpan.FromMilliseconds(200)).Subscribe(
                (x) => { result.TrySetResult(x); },
                (x) => { result.TrySetException(x.GetBaseException()); },
                () => { result.TrySetResult("Nothing"); }).ToTask();
    }
    
    { Task AlphaTask=Task.Factory.StartNew(()=>4); Func BravoFunc=x=>2*x; //准备接收,并设置过滤器以允许“Zip”提前终止 //在某些情况下。 IObservable asynchAlpha=AlphaTask.ToObservable().TakeWhile(x=>x!=5); 返回异步Alpha .Do(x=>Console.WriteLine(x))//这就是“按顺序工作”的方式 .Select(BravoFunc)//这是从Alpha映射结果的方式 //通过第二种方法。 .Timeout(TimeSpan.From毫秒(200)).Subscribe( (x) =>{result.TrySetResult(x);}, (x) =>{result.TrySetException(x.GetBaseException());}, ()=>{result.TrySetResult(“Nothing”);}).ToTask(); }

    不过,如果您想要任务,我最终会在TPL中完成这一切,或者使用
    Observable.ToTask(这个ioobservable Observable)
    而不是使用
    TaskCompletionSource

    首先,我不会返回
    TaskCompletionSource
    。这是达到目的的一种手段……应该对公共API隐藏的方法的实现细节。你的方法
    /// <summary>Extension methods for timing out tasks</summary>
    public static class TaskExtensions
    {
        /// <summary> throws an error if task does not complete before the timer.</summary>
        public static async Task Timeout(this Task t, Task timer)
        {
            var any = await Task.WhenAny(t, timer);
            if (any != t)
            {
               throw new TimeoutException("task timed out");
            }
        }
    
        /// <summary> throws an error if task does not complete before the timer.</summary>
        public static async Task<T> Timeout<T>(this Task<T> t, Task timer)
        {
            await Timeout((Task)t, timer);
            return t.Result;
        }
    
        /// <summary> throws an error if task does not complete in time.</summary>
        public static Task Timeout(this Task t, TimeSpan delay)
        {
            return t.IsCompleted ? t : Timeout(t, Task.Delay(delay));
        }
    
        /// <summary> throws an error if task does not complete in time.</summary>
        public static Task<T> Timeout<T>(this Task<T> t, TimeSpan delay)
        {
            return Timeout((Task)t, delay);
        }
    }
    
    // .. elsewhere ..
    public async Task<string> DoWorkInParallel()
    {
        var timer = Task.Delay(TimeSpan.FromMilliseconds(200));
        var alphaTask = Task.Run(() => 4);
        var betaTask = Task.Run(() => true);
    
        // wait for one of the tasks to complete
        var t = await Task.WhenAny(alphaTask, betaTask).Timeout(timer);
    
        // exit early if the task produced an invalid result
        if ((t == alphaTask && alphaTask.Result != 5) ||
            (t == betaTask && !betaTask.Result)) return "Nothing";
    
        // wait for the other task to complete
        // could also just write: await Task.WhenAll(alphaTask, betaTask).Timeout(timer);
        await ((t == alphaTask) ? (Task)betaTask : (Task)alphaTask).Timeout(timer);
    
        // unfortunately need to repeat the validation logic here.
        // this logic could be moved to a helper method that is just called in both places.
        var alpha = alphaTask.Result;
        var beta = betaTask.Result;
        return (alpha != 5 && beta) ? (alpha.ToString() + beta.ToString()) : "Nothing";
    }
    
    public async Task<string> DoWorkInSequence()
    {
        var timer = Task.Delay(TimeSpan.FromMilliseconds(200));
        var alpha = await Task.Run(() => 4).Timeout(timer);
        if (alpha != 5)
        {
            var beta = await Task.Run(() => true).Timeout(timer);
            if (beta)
            {
                return alpha.ToString() + beta.ToString();
            }
        }
    
        return "Nothing";
    }
    
    public Task<string> DoWorkInSequence()
    {
        return Task.FromResult(4)
               .Then(x => 
                     { if (x != 5)
                       {
                           return Task.FromResult(true)
                                  .Then(y => 
                                        { if (y)
                                          {
                                              return Task.FromResult(x.ToString() + y.ToString());
                                          }
                                          else
                                          {
                                              return Task.FromResult("Nothing");
                                          }
                                        });
                        }
                        else
                        {
                            return Task.FromResult("Nothing");
                        }
                     });
    }
    
    public static Task<T> Where<T>(this Task<T> task, Func<T, bool> predicate)
    {
        if (task == null) throw new ArgumentNullException("task");
        if (predicate == null) throw new ArgumentNullException("predicate");
    
        var tcs = new TaskCompletionSource<T>();
        task.ContinueWith((completed) =>
            {
                if (completed.IsFaulted) tcs.TrySetException(completed.Exception.InnerExceptions);
                else if (completed.IsCanceled) tcs.TrySetCanceled();
                else
                {
                    try
                    {
                        if (predicate(completed.Result))
                            tcs.TrySetResult(completed.Result);
                        else
                            tcs.TrySetCanceled();
                    }
                    catch (Exception ex)
                    {
                        tcs.TrySetException(ex);
                    }
                }
            });
        return tcs.Task;
    }
    
    public static Task<TResult> Select<T, TResult>(this Task<T> task, Func<T, TResult> selector)
    {
        if (task == null) throw new ArgumentNullException("task");
        if (selector == null) throw new ArgumentNullException("selector");
    
        var tcs = new TaskCompletionSource<TResult>();
        task.ContinueWith((completed) =>
        {
            if (completed.IsFaulted) tcs.TrySetException(completed.Exception.InnerExceptions);
            else if (completed.IsCanceled) tcs.TrySetCanceled();
            else
            {
                try
                {
                    tcs.TrySetResult(selector(completed.Result));
                }
                catch (Exception ex)
                {
                    tcs.TrySetException(ex);
                }
            }
        });
        return tcs.Task;
    }
    
    public static Task<T> IfCanceled<T>(this Task<T> task, T defaultValue)
    {
        if (task == null) throw new ArgumentNullException("task");
    
        var tcs = new TaskCompletionSource<T>();
        task.ContinueWith((completed) =>
        {
            if (completed.IsFaulted) tcs.TrySetException(completed.Exception.InnerExceptions);
            else if (completed.IsCanceled) tcs.TrySetResult(defaultValue);
            else tcs.TrySetResult(completed.Result);
        });
        return tcs.Task;
    }
    
    public static Task<string> DoWorkInSequence()
    {
        return (from x in Task_FromResult(5)
                where x != 5
                from y in Task_FromResult(true)
                where y
                select x.ToString() + y.ToString()
               ).IfCanceled("Nothing");
    }
    
    public Task<string> DoWorkInSequence()
    {
        var cts = new CancellationTokenSource();
    
        Task timer = Task.Factory.StartNewDelayed(200, () => { cts.Cancel(); });
    
        Task<int> AlphaTask = Task.Factory
            .StartNew(() => 4 )
            .Where(x => x != 5 && !cts.IsCancellationRequested);
    
        Task<bool> BravoTask = AlphaTask
            .Then(x => true)
            .Where(x => x && !cts.IsCancellationRequested);
    
        Task<int> DeltaTask = BravoTask
            .Then(x => 7)
            .Where(x => x != 8);
    
        Task<string> final = Task.Factory
            .WhenAny(DeltaTask, timer)
            .ContinueWith(x => !DeltaTask.IsCanceled && DeltaTask.Status == TaskStatus.RanToCompletion
                ? AlphaTask.Result.ToString() + BravoTask.Result.ToString() + DeltaTask.Result.ToString(): "Nothing");
    
        //This is here just for experimentation.  Placing it at different points
        //above will have varying effects on what tasks were cancelled at a given point in time.
        cts.Cancel();
    
        return final;
    }
    
    public Task<string> DoWorkSequentially()
    {
       Task<int> AlphaTask = Task.Run(() => 4);    //Some work;
       Task<bool> BravoTask = Task.Run(() => true);//Some other work;
    
       //Prepare for Rx, and set filters to allow 'Zip' to terminate early
       //in some cases.
       IObservable<int> AsyncAlpha = AlphaTask.ToObservable().TakeWhile(x => x != 5);
       IObservable<bool> AsyncBravo = BravoTask.ToObservable().TakeWhile(y => y);
    
        return (from alpha in AsyncAlpha
               from bravo in AsyncBravo
               select bravo.ToString() + alpha.ToString())
           .Timeout(TimeSpan.FromMilliseconds(200))
           .Concat(Observable.Return("Nothing"))   //Return Nothing if no result
           .Take(1)
           .ToTask();
    }
    
        from alpha in AsyncAlpha
        from bravo in AsyncBravo
        select bravo.ToString() + alpha.ToString()
    
        AsyncAlpha.SelectMany(a=>AsyncBravo.Select(b=> b.ToString() + a.ToString()))
    
        from a in Alpha
        from b in Bravo
        from c in Charlie
        from d in Delta
        select a+b+c+d
    
        from isConnected in _server.ConnectionState.Where(c=>c)
        from session in _server.GetSession()
        from customer in _customerServiceClient.GetCustomers(session)
        select customer;
    
        from accessToken in _oauth.Authenticate()
        from contact in _contactServiceClient.GetContact(emailAddress, accessToken)
        from imapMessageId in _mailServiceClient.Search(contact).Take(20)
        from email in _mailServiceClient.GetEmailHeaders(imapMessageId)
        select email;