后台线程使用 SynchronizationContext 更新主界面

本文介绍了一种使用SynchronizationContext替代Control.Invoke的方式,在后台线程中优雅地更新UI界面,避免了直接依赖于System.Windows.Forms,提供了更简洁的代码结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看过很多介绍文章介绍如何在后台线程更新主界面的,多数都是使用Control.Invoke, Control.BeginInvoke。这些都是很好的解决方案,不过有两个问题:

1. 必须的引用System.Windows.Forms,然后 using System.Windows.Forms

2. 代码结构比较零乱。(实际上这个也是#1 导致的)

 

M$提供了另外一个比较优雅的解决方案,就是 System.Threading. SynchronizationContext。 可以看出,它不在 namesapce System.Windows.Forms 里面,因此我们可以理直气壮地用在BusinessLaryer, Controler,甚至 module 里面。

而且它使用非常方便,只需要关注下面两个方法即可:

1.      Send:发送界面更新请求至主线程,阻塞当前线程直至返回。

2.      Post:发送界面更新请求至主线程,不阻塞当前线程。

实际上,他们都是同一个方法,只是 send 是同步,post 是异步

 

我们可以看一下下面这个例子:

点击btnThread 的时候,首先 disable btnThread,然后启动线程;接着线程主动报告进度;最后线程结束,重新 enable btnThread.

        private void btnThread_Click(object sender, EventArgs e)
        {
            txtThread.Text = @"Running background thread...";
            btnThread.Enabled = false;

            Thread t = new Thread(() =>
            {
                UiState textState = new UiState
                {
                    Control = txtThread,
                    PropertyName = "Text",
                };
                for (int i = 0; i <= 10; i++)
                {
                    Thread.Sleep(new TimeSpan(0, 0, 1));

                    textState.PropertyValue = string.Format("Progress: {0}", i * 10);
                    _context.Post(UpdateControl, textState);
                }

                Thread.Sleep(new TimeSpan(0, 0, 1));
                UiState enableState = new UiState
                {
                    Control = btnThread,
                    PropertyName = "Enabled",
                    PropertyValue = true,
                };
                _context.Post(UpdateControl, enableState);
            }
            );
            t.Start();
        }

        private static void UpdateControl(object uiObject)
        {
            UiState state = uiObject as UiState;
            if (state == null)
            {
                throw new InvalidCastException("Cannot convert object to UiState");
            }

            Type controlType = state.Control.GetType();
            PropertyInfo property = controlType.GetProperty(state.PropertyName);

            property.SetValue(state.Control, state.PropertyValue, null);
        }



这是一个很简单的例子,也许看过的人都会说:根本就没有摆脱Control  和 From 啊!

诚然如此,为了摆脱Control,我们还需要封装一下,看下面的BackgroundWorker 类:

 

    class BackgroundWorker
    {
        public EventHandler<EventArgs> WorkerStarted;
        public EventHandler<ProgressEventArgs> ReportProgress;
        public EventHandler<EventArgs> WorkerCompleted;

        public SynchronizationContext Context { get; set; }

        public void Start()
        {
            Thread t = new Thread(() =>
            {
                Context.Post(SetStartState, null);

                for (int i = 0; i <= 10; i++)
                {
                    Thread.Sleep(new TimeSpan(0, 0, 1));
                    Context.Post(UpdateProgress, i * 10);
                }

                Thread.Sleep(new TimeSpan(0, 0, 1));
                Context.Post(SetCompletedState, null);
            }
            );
            t.Start();
        }

        private void SetStartState(object state)
        {
            if (WorkerStarted != null)
            {
                WorkerStarted(this, new EventArgs());
            }
        }

        private void SetCompletedState(object state)
        {
            if (WorkerCompleted != null)
            {
                WorkerCompleted(this, new EventArgs());
            }
        }

        private void UpdateProgress(object state)
        {
            if (ReportProgress != null)
            {
                ReportProgress(this, new ProgressEventArgs {Progress = Convert.ToInt32(state)});
            }
        }
    }

有两个特别的地方:

1. 构造的时候,需要提供SynchronizationContext 实例

2. 它的三个事件(WorkerStarted,ReportProgress, WorkerCompleted),都是在 post 方法里面触发的。这样做就可以确保事件的订阅方法脱离后台线程,进入主线程。

 

使用的方法如下:

        protected override void OnLoad(EventArgs e)
        {
            _context = SynchronizationContext.Current;
        }

        private void btnWrapper_Click(object sender, EventArgs e)
        {
            btnWrapper.Enabled = false;

            BackgroundWorker worker = new BackgroundWorker {Context = SynchronizationContext.Current};
            worker.WorkerStarted += (o, args) => { txtWrapper.Text = "Running background thread..."; };
            worker.ReportProgress += (o, args) => { txtWrapper.Text = string.Format("Progress: {0}", args.Progress); };
            worker.WorkerCompleted += (o, args) =>
            {
                txtWrapper.Text = "Background thread completed";
                btnWrapper.Enabled = true;
            };

            worker.Start();
        }

在BackgroundWroker 方法里面,再也见不到任何与 Control, Form 有关的东西

在MainForm里面,使用的方法也非常简单。

效果图如下:

 


可以在这个地方下载全部代码:

 https://github.com/IGabriel/CSharpForm/tree/SynchronizationContext-Sample


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值