C# Windows WPFアプリで別スレッドからUIを操作する方法

この記事は、Windows WPFアプリ (.NET Framework)の話です。 Windows フォームアプリケーション (.NET Framework) の場合

HeavyTask_Error だと

System.InvalidOperationException: 'このオブジェクトは別のスレッドに所有されているため、呼び出しスレッドはこのオブジェクトにアクセスできません。'

というエラーが出る。

正しくUIを操作するには、HeavyTask_Fixed のように修正する。Dispatcher.BeginInvoke でデリゲートを非同期に実行している。

//エラーが出る
Othersbutton.IsEnabled = false;

// 修正後 {内は複数行でも可} awaitなので関数の属性にもawaitを付ける
await Dispatcher.BeginInvoke(new Action(() =>{ Othersbutton.IsEnabled = false; }));

コード全体は以下の通り

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

namespace WpfApp1
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        bool stop = false;
        public MainWindow()
        {
            InitializeComponent();
        }
        void HeavyTask_Error()
        {
            Othersbutton.IsEnabled = false;
            for (int i = 0; i < 30; i++)
            {
                System.Threading.Thread.Sleep(100);
                if (stop) break;
                label1.Text = i.ToString();
            }
            if (stop)
            {
                label1.Text = "Stop!";
            }
            else
            {
                label1.Text = "Completed!";
            }
            Othersbutton.IsEnabled = true;
        }
        delegate void MethodInvoker();
        async Task HeavyTask_FixedAsync()
        {
            await Dispatcher.BeginInvoke(new Action(() =>{ Othersbutton.IsEnabled = false; }));
            for (int i = 0; i < 30; i++)
            {
                System.Threading.Thread.Sleep(100);
                if (stop) break;
                await Dispatcher.BeginInvoke(new Action(() => { label1.Text = i.ToString(); }));
            }
            if (stop)
            {
                await Dispatcher.BeginInvoke(new Action(() => { label1.Text = "Stop!"; }));
            }
            else
            {
                await Dispatcher.BeginInvoke(new Action(() => { label1.Text = "Completed!"; }));
            }
            await Dispatcher.BeginInvoke(new Action(() => { Othersbutton.IsEnabled = true; }));
        }
        private void StartError_Click(object sender, RoutedEventArgs e)
        {
            stop = false;
            Task.Run(() => HeavyTask_Error());
        }
        private void StartFixed_Click(object sender, RoutedEventArgs e)
        {
            stop = false;
            Task.Run(() => HeavyTask_FixedAsync());
        }
        private void Stop_Click(object sender, RoutedEventArgs e)
        {
            stop = true;
        }
    }
}

f:id:onsanai:20210924110422p:plain