C#で非同期に処理するTask間のメッセージ通信

はじめに

C#でTaskを使用した非同期処理をする場合に複数スレッド間でメッセージのやりとりをする方法をまとめます。

実行環境

事前準備

MVVM Light ToolkitのMessengerを使用するのでNuGetでインストールします。 f:id:hiroki-sawano:20180907062443p:plain

プロキシの設定が必要な場合はNuGet.Configに次の記述を追加します。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="http_proxy" value="http://~~" />
    <add key="http_proxy.user" value="user_name" />
  </packageSources>
</configuration>

サンプルプログラムの構成

ここではTaskATaskBが並列実行している中で、TaskAがとあるイベントを契機にTaskBにメッセージを送信し、TaskBはメッセージを処理し終えたことをTaskAに通知するプログラムを想定します。 f:id:hiroki-sawano:20180907073634p:plain

実装

まずはじめにProgram::Main()で2つのTaskrun()します。

class Program
{
    static void Main(string[] args)
    {
        TaskA taskA = new TaskA();
        TaskB taskB = new TaskB();

        var tokenSource = new CancellationTokenSource();
        var token = tokenSource.Token;

        var tasks = new[]
        {
            Task.Run(() => taskA.run(token), token),
            Task.Run(() => taskB.run(token), token)
        };
        Task.WaitAll(tasks);
    }
}

続けてTask側ですが、各Taskのコンストラクタではメッセージ受信時に起動したいメソッドをMessenger.Default.Registerで登録しています。
TaskAResponseMessage型のインスタンスMessenger.Default.Sendされたときに、TaskA::onReceivedMessage(ResponseMessage res)を実行します。
TaskBRequestMessage型のインスタンスMessenger.Default.Sendされたときに、TaskB::onReceivedMessage(RequestMessage req)を実行します。
Messenger.Default.SendはメッセージをMessengerに登録している全タスクにブロードキャストしてしまうので、メッセージの型を変えることでメッセージの送信先を制御しています。

class TaskA
{
    public TaskA()
    {
        Messenger.Default.Register<ResponseMessage>(this, onReceivedMessage);
    }
    public void run(CancellationToken token)
    {
        // do something
        RequestMessage req = new RequestMessage();
        req.msg = "TaskA's message.";
        Messenger.Default.Send<RequestMessage>(req);
    }
    private void onReceivedMessage(ResponseMessage res)
    {
        Console.WriteLine("TaskB sent the following message : " + res.msg);
    }
}

class TaskB
{
    public TaskB()
    {
        Messenger.Default.Register<RequestMessage>(this, onReceivedMessage);
    }

    public void run(CancellationToken token)
    {
        //do something
    }

    private void onReceivedMessage(RequestMessage req)
    {
        Console.WriteLine("TaskA sent the following message : " + req.msg);
        ResponseMessage res = new ResponseMessage();
        res.msg = "TaskB's message.";
        Messenger.Default.Send(res);
    }
}

class RequestMessage
{
    public string msg { get; set; }
}

class ResponseMessage
{
    public string msg { get; set; }
}

さいごに

これでひとまずやりたいことはできましたが、より良い方法が見つかったら追記します。