zoukankan      html  css  js  c++  java
  • 富客户端 wpf, Winform 多线程更新UI控件

    前言  

    在富客户端的app中,如果在主线程中运行一些长时间的任务,那么应用程序的UI就不能正常相应。因为主线程要负责消息循环,相应鼠标等事件还有展现UI。

    因此我们可以开启一个线程来格外处理需要长时间的任务,但在富客户端中只有主线程才能更新UI的控件。

    解决方法

    简单的来说,我们需要从其他的线程来更新UI线程的控件,需要将这个操作转交给UI线程(线程marshal)。

    方法1:

    在底层的操作中,可以有以下的方法:

    • WPF中,在element的Dispatcher类中调用BeginInvoke或者Invoke方法
    • Metro中,在Dispatcher类中调用RunAsync或者Invoke方法
    • Winform中,在控件中直接调用BeginInvoke或者Invoke方法

    以上所有的方法的参数都是一个Delegate,用此Delegate来代表需要处理的任务:

    public IAsyncResult BeginInvoke(Delegate method);

    BeginInvoke/RunAsync方法是将这个 Delegate推送到UI线程的消息队列中,这个消息队列也就是前面提到的鼠标,键盘事件等队列。

    Invoke方法也是推送delegate到消息队列,但还会一直阻塞到此delegate被UI线程处理为止。所以一般来说我们还是用BeginInvoke/RunAsync方法。

    对应app来说,我们可以将其想象为一下的伪代码:

    while (!thisApplication.Ended)
    {
    wait for something to appear in message queue
    Got something: what kind of message is it?
    Keyboard/mouse message -> fire an event handler
    User BeginInvoke message -> execute delegate
    User Invoke message -> execute delegate & post result
    }

    那接下来我们用winform来demo一下:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            new Thread(work).Start();
        }
    
        void work()
        {
            Thread.Sleep(5000);
            UpdateMessage("july Luo thread Test");
        }
    
        void UpdateMessage(string message)
        {
            Action action = () => lblJulyLuo.Text = message;
            this.BeginInvoke(action);
        }
    }

    方法2

    在 System.ComponentModel命名空间中,有 SynchronizationContext抽象类,此类也可以处理线程marshal。

    在wpf,metro, winform中都定义了此类的子类,而且可以用SynchronizationContext.Current获取,然后调用Post方法,可以理解为将其他线程的任务post到UI线程中。

    一下为demo:

    public partial class Form1 : Form
    {
        SynchronizationContext _uiSyncContext;
    
        public Form1()
        {
            InitializeComponent();
            new Thread(() => work()).Start();
            _uiSyncContext = SynchronizationContext.Current;
        }
    
        void work()
        {
            Thread.Sleep(5000);
            UpdateMessage("july Luo thread Test");
        }
    
        void UpdateMessage(string message)
        {
            _uiSyncContext.Post(_ => lblJulyLuo.Text = message, null);
        }
    }
    

    SynchronizationContext类还有一个Send方法,和我们上面提到的Invoke方法的作用一致。

    当然了还有BackgroundWorker类,此类在内部用了SynchronizationContext,所以其也可在其他线程中更新UI线程。

    方法3

    在.net 4.0之后,已经有了TPL供我们方便的操作多线程,这里试用Task类也可以完成类似操作,demo如下:

    public partial class Form1 : Form
    {
        Task<string> t;
    
        public Form1()
        {
            InitializeComponent();
            t = Task.Run(() => work());
            var awaiter = t.GetAwaiter();
            awaiter.OnCompleted(() =>
            {
                string message = awaiter.GetResult();
                lblJulyLuo.Text = message;
            });
        }
    
        string work()
        {
            Thread.Sleep(5000);
            return "july Luo thread Test";
        }
    }
    

     这里和上面有点不同,我们使用Task来代替多线程,而且其返回要更新的字符串,如何调用GetAwaiter方法返回需要的awaiter,最后在awaiter中的OnCompleted方法中直接更新控件。

    原理就是awaiter中的OnCompleted方法会自动获取synchronizationContext,也会将其推送到UI线程的消息队列中。

     方法4

    使用TaskScheduler来marshal线程,TaskScheduler是抽象类,其负责分配管理Task对象。

    Framework中有两个实现:一个就是 default scheduler 其负责串联CLR中的线程池,还有一个就是synchronization context scheduler,其负责解决其他线程需要更新UI控件的。

    所以思路就是用Task实现多线程,然后调用其ContinueWith方法,并传入对应的TaskScheduler:

    public partial class Form1 : Form
    {
        TaskScheduler _uiScheduler;
        Task<string> t;
    
        public Form1()
        {
            InitializeComponent();
            _uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            t = Task.Run(() => work()).ContinueWith(t => lblJulyLuo.Text = t.Result, _uiScheduler);
        }
    
        string work()
        {
            Thread.Sleep(5000);
            return "july Luo thread Test";
        }
    }
    

    总结

    富客户端中UI线程一直会处理着消息循环,无论使用那种方法都是将其推送到消息队列中以便UI线程处理。

  • 相关阅读:
    设计模式之一(策略模式)
    电脑开机进入不了XP界面
    IBM X系列笔记本通过U盘安装系统方法全攻略
    DELPHI 访问其它电脑文件(局域网)
    笔记本维修小插曲 屏幕不亮处理方式
    delphi 笔记
    电脑小子的新婚夜
    如何得到动态链接库的输出函数(delphi tdump.exe)
    sql server重复数据处理
    如何使用jQuery向asp.net Mvc传递复杂json数据Filter篇
  • 原文地址:https://www.cnblogs.com/julyluo/p/5522944.html
Copyright © 2011-2022 走看看