使用TPL通过使用continueWith子句

时间:2016-06-13 08:18:52

标签: c# task-parallel-library continuewith

首先,我将解释我想要做的事情。

我有组件A 使用组件B

为了在两者之间进行通信,我需要使用事件。

我的一个先决条件是让组件B 以异步方式运行并以连续顺序运行事件处理程序。

此外,我想取消通话管道(当用户询问时)。因此,所有尚未执行的事件处理程序将永远不会执行。

实现的解决方案是TPL。我做了一个关于我试图做的事情的POC:

    static void Main(string[] args)
    {
        var tokenSource = new CancellationTokenSource();
        var token = tokenSource.Token;

        var t = Task.Factory.StartNew(() => DoSomeWork(token));
                            //.ContinueWith((prevTask) => DoSomeWork(token));

        t.ContinueWith((prevTask) => DoSomeWork(token));

        Task.WaitAll(t);

        Console.WriteLine("Finish");

        Console.ReadKey();
    }

    static int id = 1;
    static void DoSomeWork(CancellationToken ct)
    {
        ct.ThrowIfCancellationRequested();

        Thread.Sleep(1000);

        Console.WriteLine(id++);
    }

此代码段的输出:

  

1

     

完成

     

2

正如你所看到的,它在真正完成之前完成。它在完成后显示 2

如果我通过此修改前面的代码,它可以工作:

        static void Main(string[] args)
    {
        var tokenSource = new CancellationTokenSource();
        var token = tokenSource.Token;

        var t = Task.Factory.StartNew(() => DoSomeWork(token))
                            .ContinueWith((prevTask) => DoSomeWork(token));

        //t.ContinueWith((prevTask) => DoSomeWork(token));

        Task.WaitAll(t);

        Console.WriteLine("Finish");

        Console.ReadKey();
    }

    static int id = 1;
    static void DoSomeWork(CancellationToken ct)
    {
        ct.ThrowIfCancellationRequested();

        Thread.Sleep(1000);

        Console.WriteLine(id++);
    }

此代码段的输出:

  

1

     

2

     

完成

如您所知,我不需要在任务声明中使用continueWith语句,但是在引发事件时。

为什么Task.WaitAll(t);第一个样本不起作用吗?

有人可以帮助我吗?

2 个答案:

答案 0 :(得分:1)

在C#中进行异步编码的正确方法是使用await关键字。

public async Task DoLotsOfWork()
{
    await DoSomeWorkAsync();
    await DoSomeMoreWorkAsync();
    Console.WriteLine("Finish");
}

从控制台应用程序运行该代码时会遇到一些问题,因此我建议您使用@ StephenCleary的Task.AsyncEx库。

https://www.nuget.org/packages/Nito.AsyncEx/

你这样使用它。

public void Main()
{
    AsyncContext.Run(DoLotsOfWork);
}

此外。使用Task.Run(或更差Task.Factory.StartNew)方法的原因很少。这些在后台运行你的方法作为Threadpool工作。

例如

private static async Task DoSomeWorkAsync(CancellationToken ct)
{
    await Task.Delay(TimeSpan.FromMilliseconds(1000), ct);
    Console.WriteLine(id++);
}

这不会在任何线程上运行(因此不会阻塞任何线程)。而是创建一个计时器/回调,以使主线程在1000毫秒后返回到第二行

编辑:要动态地执行此操作,这也非常简单

public async Task DoLotsOfWork(IEnumerable<Func<Task>> tasks)
{
    foreach(var task in tasks)
        await task();
    Console.WriteLine("Finished");
}

如果你问一下使用糟糕的EAP模式的方法,我会建议你使用Rx的Observable.FromEventPattern辅助函数。

public async Task SendEmail(MailMessage message)
{
    using(var smtp = new SmtpClient())
    {
        smtp.SendAsync(message);
        await Observable.FromEventPattern<>(x => smtp.SendCompleted +=x, x => smtp.SendCompleted -=x)
                  .ToTask()
    }
}

进一步编辑:

public class Publisher
{
    public IObservable<CancelationToken> SomeEvent {get;}
}

public abstract class Subscriber
{
    public abstract IObservable<CancelationToken> Subscribe(IObservable<CancelationToken> observable);

}

IEnumerable<Subscriber> subscribers = ...
Publisher publisher = ...

IDisposable subscription = subscribers.Aggregate(publisher.SomeEvent, (e, sub) => sub.Subscribe(e)).Subscribe();

//Dispose the subscription when you want to stop listening.

答案 1 :(得分:1)

最初的问题是你正在创建两个任务,但只等待一个任务。

// t is now the continuation task
var t = Task.Factory.StartNew(() => DoSomeWork(token))
             .ContinueWith((prevTask) => DoSomeWork(token));
Task.WaitAll(t); // <-- wait till the continuation task has finished

第二个片段会发生什么,你正在等待继续任务,所以它会等到它完成

// t is the "first" task
var t = Task.Factory.StartNew(() => DoSomeWork(token));
// The continuation task is assigned to "t2"
var t2 = t.ContinueWith((prevTask) => DoSomeWork(token));
Task.WaitAll(new [] { t, t2 } ); // <-- wait for all tasks
Console.WriteLine("Finish");

所以,第二种方法没问题,但如果你想要更精细的控制,只需指定一个任务变量来等待继续任务:

WaitAll

注意:我已按照您的代码进行了示例,但Task.Wait并未将单个任务作为参数(它需要一系列任务),因此可能不编译。您可以使用WaitAll或将数组传递给@Override public void onBackPressed(){ handler.removeCallbacksAndMessages(null); new leftRoom().execute(); getSupportFragmentManager().beginTransaction().replace(R.id.viewpager, new Fragment_01_Rooms()).commit(); }