0

まず、私がやろうとしていることを説明します。

コンポーネント Bを使用しているコンポーネント Aがあります。

両者の間で通信するには、イベントを使用する必要があります。

ここでの前提条件の 1 つは、コンポーネント 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

最初の問題は、2 つのタスクを作成しているのに 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

2 番目のスニペットで何が起こるかというと、継続タスクを待機しているため、完了するまで待機します。

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

したがって、2 番目の方法は問題ありませんが、より細かい制御が必要な場合は、継続タスクを待機するタスク変数を割り当てるだけです。

// 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 に答える
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 ミリ秒後にメイン スレッドが 2 行目に戻ります。

編集:これを動的に行うには、それも非常に簡単です

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 に答える