WPFで同期処理を非同期処理として実行する非同期ラッパーメソッドの実装

コンピュータ

処理のロジックは、まず同期処理として作成し、コンソールアプリケーションなどで動作を確認することが多いでしょう。
しかし、その処理をGUIアプリケーションから呼び出す場合、処理時間が長いとUIスレッドをブロックしてしまうため、非同期処理として実行する必要があります。

このような場合、処理本体は同期メソッドのままにしておき、それを非同期で実行する「非同期ラッパーメソッド」を用意することで、既存のコードを大きく変更せずにGUIアプリケーションへ組み込むことができます。

ソースコード

MainWindow.xaml

<Window x:Class="WpfSample01.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="AsyncSample" Height="200" Width="300">

    <StackPanel Margin="20">

        <Button Name="StartButton"
                Click="StartButton_Click"
                Margin="0,0,0,10"
                Content="開始" />

        <Button Name="CancelButton"
                Click="CancelButton_Click"
                Margin="0,0,0,10"
                Content="キャンセル" />

        <TextBlock Name="StatusText"
                   FontSize="16"/>

    </StackPanel>
</Window>

MainWindow.xaml.cs

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;

namespace WpfSample01;

public partial class MainWindow : Window
{
    CancellationTokenSource? _cts;

    public MainWindow()
    {
        InitializeComponent();
    }

    private async void StartButton_Click(object sender, RoutedEventArgs e)
    {
        _cts = new CancellationTokenSource();

        try
        {
            StatusText.Text = "処理中...";

            await Worker.DoWorkAsync(_cts.Token);

            StatusText.Text = "完了";
        }
        catch (OperationCanceledException)
        {
            StatusText.Text = "キャンセルされました";
        }
        finally
        {
            _cts.Dispose();
            _cts = null;
        }
    }

    private void CancelButton_Click(object sender, RoutedEventArgs e)
    {
        _cts?.Cancel();
    }
}

Worker.cs

using System.Threading;
using System.Threading.Tasks;

namespace WpfSample01;

public static class Worker
{
    // 通常メソッド(同期)
    public static void DoWork(CancellationToken token = default)
    {
        for (int i = 0; i < 10; i++)
        {
            token.ThrowIfCancellationRequested();

            // 重い処理の代わり
            Task.Delay(500, token).GetAwaiter().GetResult();
        }
    }

    // Asyncラッパー(非同期)
    public static Task DoWorkAsync(CancellationToken token = default)
    {
        return Task.Run(() => DoWork(token), token);
    }
}

重い処理の代わりに、
Task.Delay() を使った少し見慣れないコードを書いていますが、
これはあくまでサンプル用の処理です。

実際のアプリケーションでは、この部分に 同期で実行される重たい処理(画像処理や計算処理など)を配置します。
今回の例では、その処理を非同期ラッパーメソッドから呼び出すことで、UIスレッドをブロックしない構成を説明しています。


実行例

起動後、「開始」ボタンを押すと処理が開始されます。

処理中に「キャンセル」ボタンを押すと、処理が途中でキャンセルされます。

一見すると何気ない動作ですが、処理の実行中でも「キャンセル」ボタンを押せるのは、
処理を非同期で実行しているためです。同期処理の場合、処理が終わるまでUIスレッドが
ブロックされるため、ボタン操作を受け付けることができません。

コメント