0

首先,我将解释我要做什么。

我有一个组件 A正在使用组件 B

为了在它们之间进行通信,我需要使用 event.

我的先决条件之一是让组件 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

如您所见,它在它真正完成之前完成。它在Finish之后显示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); 第一个样品不起作用吗?

有人可以帮助我吗?

4

2 回答 2

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.
于 2016-06-13T08:55:06.487 回答
1

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

// t is the "first" task
var t = Task.Factory.StartNew(() => DoSomeWork(token));
// the continuation task is not assigned
t.ContinueWith((prevTask) => DoSomeWork(token));
Task.WaitAll(t); // <-- wait only on "t", which is the first task
Console.WriteLine("Finish"); // when the first task finishes, this gets printed
// now the continuation task is executing, but you are not waiting for it

第二个片段发生的是您正在等待继续任务,所以它会等到它完成

// 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

于 2016-06-13T09:48:53.850 回答