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线程处理。

  • 相关阅读:
    CSS盒子模型
    getContextPath、getServletPath、getRequestURI、request.getRealPath的区别
    MYSQL中的CASE WHEN END AS
    单点登录的精华总结
    git&github
    June 21st 2017 Week 25th Wednesday
    June 20th 2017 Week 25th Tuesday
    June 19th 2017 Week 25th Monday
    June 18th 2017 Week 25th Sunday
    June 17th 2017 Week 24th Saturday
  • 原文地址:https://www.cnblogs.com/julyluo/p/5522944.html
Copyright © 2011-2022 走看看