yotiky Tech Blog

とあるエンジニアの備忘録

Unity - System.Threading.Channels で生産者/消費者パターンを利用する

今回は、System.Threading.Channels を Unity に導入した実装例です。 次の記事を参考にさせて頂きました。

qiita.com

前回の記事もご参考までに。

Unity - UniTask の Channel で生産者/消費者パターンを利用する - yotiky Tech Blog

検証環境

以下の環境で、Unity Editor 上で実行しています。

  • Unity 2019.3.15f1
  • Script Backend : Mono
  • Api Compability Lebel : .NET 4.x
  • System.Threading.Channels 4.7.1
  • system.threading.tasks.extensions.4.5.4
  • system.runtime.compilerservices.unsafe.4.7.1

目次

導入

まずは System.Threading.Channels を Unity プロジェクトに導入します。 以下のサイトより package をダウンロードし、zip ファイルにリネームして解凍します。System.Threading.Channels.dll を Unity プロジェクトにD&Dで。 Dependencies なライブラリがあるの続く2つのパッケージも同様にして、Unity に入れればOKです。

www.nuget.org

NuGet Gallery | System.Threading.Tasks.Extensions 4.5.4

NuGet Gallery | System.Runtime.CompilerServices.Unsafe 4.7.1

実装例

生産者 1 : 消費者 1

1対1の実装例です。ほとんど参考にさせて頂いた記事のコードのままです。

    async Task Single()
    {
        var channel = Channel.CreateUnbounded<int>(
            new UnboundedChannelOptions
            {
                SingleReader = true,
                SingleWriter = true,
            });

        var consumer = Task.Run(async () =>
        {
            while (await channel.Reader.WaitToReadAsync())
            {
                Debug.Log(await channel.Reader.ReadAsync());
            }
        });

        var producer = Task.Run(async () =>
        {
            await channel.Writer.WriteAsync(1);
            await channel.Writer.WriteAsync(2);
            await channel.Writer.WriteAsync(3);
            channel.Writer.Complete();
        });

        await Task.WhenAll(consumer, producer);

        Debug.Log("Completed.");
    }

実行結果です。

1
2
3

生産者 n : 消費者 1

n対1の実装例です。

    async Task MultiToSingle()
    {
        var channel = Channel.CreateUnbounded<int>(
            new UnboundedChannelOptions
            {
                SingleReader = true,
            });

        var consumer = Task.Run(async () =>
        {
            while (await channel.Reader.WaitToReadAsync())
            {
                Debug.Log("Producer" + await channel.Reader.ReadAsync());
            }
        });

        var producers = Enumerable.Range(1, 3)
            .Select(producerId =>
                Task.Run(async () =>
                {
                    await channel.Writer.WriteAsync(producerId);
                }));

        await Task.WhenAll(producers);
        channel.Writer.Complete();

        await consumer;

        Debug.Log("Completed.");
    }

実行結果です。

Producer1
Producer2
Producer3

生産者 1 : 消費者 n

最後に 1対nの実装例です。先に説明したとおり、複数の消費者はキューを消費するわけではなく、等しく購読するので注意が必要です。

    async Task SingleToMulti()
    {
        var channel = Channel.CreateUnbounded<int>(
            new UnboundedChannelOptions
            {
                SingleWriter = true,
            });

        var consumers = Enumerable.Range(1, 3)
            .Select(consumerId =>
                Task.Run(async () =>
                {
                    while (await channel.Reader.WaitToReadAsync())
                    {
                        if (channel.Reader.TryRead(out var value))
                        {
                            Debug.Log($"Consumer{consumerId}:{value}");
                        }
                    }
                }));

        var producer = Task.Run(async () =>
        {
            await channel.Writer.WriteAsync(1);
            await channel.Writer.WriteAsync(2);
            await channel.Writer.WriteAsync(3);
            channel.Writer.Complete();
        });

        await Task.WhenAll(consumers.Union(new[] { producer }));

        Debug.Log("Completed.");
    }

実行結果です。

Consumer2:3
Consumer1:1
Consumer3:2