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

来源:互联网 发布:手机壁纸大全软件 编辑:程序博客网 时间:2024/05/16 05:53

看过很多介绍文章介绍如何在后台线程更新主界面的,多数都是使用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


0 0