C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > WPF多线程更新UI

WPF多线程更新UI的两种实用方案

作者:小码编匠

在WPF开发中,使用多线程处理耗时任务是常见做法,但若尝试在后台线程直接修改UI元素,系统会抛出异常,本文将详细介绍WPF 多线程更新UI的两种实用方案,需要的朋友可以参考下

前言

在WPF开发中,使用多线程处理耗时任务是常见做法。但若尝试在后台线程直接修改UI元素,系统会抛出异常:“调用线程无法访问此对象,因为另一个线程拥有该对象。” 这是因为WPF的UI元素只能由创建它的主线程(即UI线程)访问。

在单线程应用中,所有代码都在UI线程执行,因此可以随意更新界面。一旦引入多线程,就必须通过特定机制将UI更新操作“转发”回UI线程。本文介绍两种有效解决方案:Dispatcher 和 TaskScheduler。

问题再现

为便于理解,先构建一个典型错误场景。

XAML布局

<Window x:Class="UpdateUIDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="130" Width="363">
    <Canvas>
        <TextBlock Width="40" Canvas.Left="38" Canvas.Top="27" Height="29" 
                   x:Name="first" Background="Black" Foreground="White"/>
        <TextBlock Width="40" Canvas.Left="128" Canvas.Top="27" Height="29" 
                   x:Name="second" Background="Black" Foreground="White"/>
        <TextBlock Width="40" Canvas.Left="211" Canvas.Top="27" Height="29" 
                   x:Name="Three" Background="Black" Foreground="White"/>
        <Button Height="21" Width="50" Canvas.Left="271" Canvas.Top="58" 
                Content="开始" Click="Button_Click"/>
    </Canvas>
</Window>

错误的后台线程代码

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Task.Factory.StartNew(Work);
    }

    private void Work()
    {
        Task task = new Task((tb) => Begin(this.first), this.first);
        Task task2 = new Task((tb) => Begin(this.second), this.first);
        Task task3 = new Task((tb) => Begin(this.Three), this.first);
        
        task.Start();
        task.Wait();
        task2.Start();
        task2.Wait();
        task3.Start();
    }

    private void Begin(TextBlock tb)
    {
        int i = 100000000;
        while (i > 0)
        {
            i--;
        }
        Random random = new Random();
        string num = random.Next(0, 100).ToString();
        tb.Text = num; // 错误:跨线程访问UI
    }
}

运行程序并点击“开始”按钮,会立即出现跨线程异常,程序崩溃。

解决方案

方法一:使用 Dispatcher

Dispatcher 是WPF内置的消息调度器,负责将工作项排队到UI线程。

修改 Begin 方法:

private void Begin(TextBlock tb)
{
    int i = 100000000;
    while (i > 0)
    {
        i--;
    }
    Random random = new Random();
    string num = random.Next(0, 100).ToString();

    // 将UI更新操作分发到UI线程
    Action<TextBlock, string> updateAction = UpdateTb;
    tb.Dispatcher.BeginInvoke(updateAction, tb, num);
}

// 执行UI更新的实际方法
private void UpdateTb(TextBlock tb, string text)
{
    tb.Text = text;
}

说明: BeginInvoke 异步执行委托,不会阻塞后台线程。若需等待执行完成,可使用 Invoke

方法二:使用 TaskScheduler

通过 TaskScheduler.FromCurrentSynchronizationContext() 创建一个绑定当前UI线程上下文的调度器。

完整修正代码:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    // 在UI线程创建,捕获UI同步上下文
    private readonly TaskScheduler _uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Task.Factory.StartNew(SchedulerWork);
    }

    private void SchedulerWork()
    {
        Task.Factory.StartNew(Begin, first).Wait();
        Task.Factory.StartNew(Begin, second).Wait();
        Task.Factory.StartNew(Begin, Three).Wait();
    }

    private void Begin(object obj)
    {
        TextBlock tb = obj as TextBlock;
        int i = 100000000;
        while (i > 0)
        {
            i--;
        }
        Random random = new Random();
        string num = random.Next(0, 100).ToString();

        // 使用UI调度器执行UI更新任务
        Task.Factory.StartNew(
            () => UpdateTb(tb, num),
            CancellationToken.None,
            TaskCreationOptions.None,
            _uiScheduler).Wait();
    }

    private void UpdateTb(TextBlock tb, string text)
    {
        tb.Text = text;
    }
}

说明: _uiScheduler 在窗口构造函数中创建,此时处于UI线程,能正确捕获上下文。后续任务通过该调度器执行,确保在UI线程运行。

总结

两种方法都能有效解决WPF多线程UI更新问题:

开发者可根据项目需求和个人偏好选择合适的方法。这两种技术同样适用于WinForms等其他UI框架。

以上就是WPF多线程更新UI的两种实用方案的详细内容,更多关于WPF多线程更新UI的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文