在WPF中使用多線程更新UI
有經(jīng)驗(yàn)的程序員們都知道:不能在UI線程上進(jìn)行耗時(shí)操作,那樣會(huì)造成界面卡頓,如下就是一個(gè)簡(jiǎn)單的示例:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Dispatcher.Invoke(new Action(()=> { }));
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.Content = new UserControl1();
}
}
class UserControl1 : UserControl
{
TextBlock textBlock;
public UserControl1()
{
textBlock = new TextBlock();
this.Content = textBlock;
this.Dispatcher.BeginInvoke(new Action(updateTime), null);
}
private async void updateTime()
{
while (true)
{
Thread.Sleep(900); //模擬耗時(shí)操作
textBlock.Text = DateTime.Now.ToString();
await Task.Delay(100);
}
}
}當(dāng)我們運(yùn)行這個(gè)程序的時(shí)候,就會(huì)發(fā)現(xiàn):由于主線程大部分的時(shí)間片被占用,無法及時(shí)處理系統(tǒng)事件(如鼠標(biāo),鍵盤等輸入),導(dǎo)致程序變得非??D,連拖動(dòng)窗口都變得不流暢;
如何解決這個(gè)問題呢,初學(xué)者可能想到的第一個(gè)方法就是新啟一個(gè)線程,在線程中執(zhí)行更新:
public UserControl1()
{
textBlock = new TextBlock();
this.Content = textBlock;
ThreadPool.QueueUserWorkItem(_ => updateTime());
}但很快就會(huì)發(fā)現(xiàn)此路不通,因?yàn)閃PF不允許跨線程訪問程序,此時(shí)我們會(huì)得到一個(gè):"The calling thread cannot access this object because a different thread owns it."的InvalidOperationException異常

那么該如何解決這一問題呢?通常的做法是把耗時(shí)的函數(shù)放在線程池執(zhí)行,然后切回主線程更新UI顯示。前面的updateTime函數(shù)改寫如下:
private async void updateTime()
{
while (true)
{
await Task.Run(() => Thread.Sleep(900));
textBlock.Text = DateTime.Now.ToString();
await Task.Delay(100);
}
}這種方式能滿足我們的大部分需求。但是,有的操作是比較耗時(shí)間的。例如,在多窗口實(shí)時(shí)監(jiān)控的時(shí)候,我們就需要同時(shí)多十來個(gè)屏幕每秒鐘各進(jìn)行幾十次的刷新,更新圖像這個(gè)操作必須在UI線程上進(jìn)行,并且它有非常耗時(shí)間,此時(shí)又會(huì)回到最開始的卡頓的情況。
看起來這個(gè)問題無法解決,實(shí)際上,WPF只是不允許跨線程訪問程序,并非不允許多線程更新界面。我們大可以對(duì)每個(gè)視頻監(jiān)控窗口單獨(dú)其一個(gè)獨(dú)立的線程,在那個(gè)線程中進(jìn)行更新操作,此時(shí)就不會(huì)影響到主線程。MSDN上有篇文章介紹了詳細(xì)的操作:Multithreaded UI: HostVisual。用這種方式將原來的程序改寫如下:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
HostVisual hostVisual = new HostVisual();
UIElement content = new VisualHost(hostVisual);
this.Content = content;
Thread thread = new Thread(new ThreadStart(() =>
{
VisualTarget visualTarget = new VisualTarget(hostVisual);
var control = new UserControl1();
control.Arrange(new Rect(new Point(), content.RenderSize));
visualTarget.RootVisual = control;
System.Windows.Threading.Dispatcher.Run();
}));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
public class VisualHost : FrameworkElement
{
Visual child;
public VisualHost(Visual child)
{
if (child == null)
throw new ArgumentException("child");
this.child = child;
AddVisualChild(child);
}
protected override Visual GetVisualChild(int index)
{
return (index == 0) ? child : null;
}
protected override int VisualChildrenCount
{
get { return 1; }
}
}這個(gè)里面用來了兩個(gè)新的類:HostVisual、VisualTarget。以及自己寫的一個(gè)VisualHost。MSDN上相關(guān)的解釋,也不算難理解,這里就不多介紹了。最后,再來重構(gòu)一下代碼,把在新線程中創(chuàng)建控件的方式改寫如下:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
createChildInNewThread<UserControl1>(this);
}
void createChildInNewThread<T>(ContentControl container)
where T : UIElement , new()
{
HostVisual hostVisual = new HostVisual();
UIElement content = new VisualHost(hostVisual);
container.Content = content;
Thread thread = new Thread(new ThreadStart(() =>
{
VisualTarget visualTarget = new VisualTarget(hostVisual);
var control = new T();
control.Arrange(new Rect(new Point(), content.RenderSize));
visualTarget.RootVisual = control;
System.Windows.Threading.Dispatcher.Run();
}));
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}當(dāng)然,我這個(gè)函數(shù)多了一些不必要的的限制:容器必須是ContentControl,子元素必須是UIElement??梢愿鶕?jù)實(shí)際需要進(jìn)行相關(guān)修改。這里有一個(gè)完整的示例,也可以參考一下。
到此這篇關(guān)于WPF使用多線程更新UI的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C# 在項(xiàng)目中引用x86 x64的非托管代碼的方法
使用宏最簡(jiǎn)單的方法是編譯兩個(gè)版本,編譯多個(gè)版本可以點(diǎn)擊配置管理器,然后創(chuàng)建x86和x64,然后版本添加宏,這樣就可以判斷宏來使用不同的dll。這篇文章主要介紹了C# 在項(xiàng)目中引用x86 x64的非托管代碼的方法,需要的朋友可以參考下2018-03-03
基于WebClient實(shí)現(xiàn)Http協(xié)議的Post與Get對(duì)網(wǎng)站進(jìn)行模擬登陸和瀏覽實(shí)例
這篇文章主要介紹了基于WebClient實(shí)現(xiàn)Http協(xié)議的Post與Get對(duì)網(wǎng)站進(jìn)行模擬登陸和瀏覽的方法,以實(shí)例形式詳細(xì)分析了WebClient模擬POST與GET登陸與瀏覽的過程,對(duì)于C#項(xiàng)目開發(fā)來說具有不錯(cuò)的參考借鑒價(jià)值,需要的朋友可以參考下2014-11-11
C#?利用Autofac批量接口注入依賴的問題小結(jié)
這篇文章主要介紹了C#?利用Autofac批量接口注入依賴的問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12

