前言
在WPF中配猫,在使用多線程在后臺(tái)進(jìn)行計(jì)算限制的異步操作的時(shí)候,如果在后臺(tái)線程中對(duì)UI進(jìn)行了修改杏死,則會(huì)出現(xiàn)一個(gè)錯(cuò)誤:(調(diào)用線程無法訪問此對(duì)象泵肄,因?yàn)榱硪粋€(gè)線程擁有該對(duì)象。)這是很常見的一個(gè)錯(cuò)誤淑翼,一不小心就會(huì)有這個(gè)現(xiàn)象腐巢。在WPF中,如果不是用多線程的話玄括,例如單線程應(yīng)用程序冯丙,就是說代碼一路過去都在GUI線程運(yùn)行,可以隨意更新任何東西遭京,包括UI對(duì)象胃惜。但是使用多線程來更新UI就可能會(huì)出現(xiàn)以上所說問題,怎么解決哪雕?本文章提供兩個(gè)方法:Dispatcher(大部分人使用),TaskScheduler(任務(wù)調(diào)度器)船殉。
問題再現(xiàn)
可能有的WPF新手不懂這是什么情況,先來個(gè)問題的再現(xiàn)斯嚎,再使用本文章的兩個(gè)方法進(jìn)行解決利虫。
為了演示方便,我使用了最簡(jiǎn)單的布局堡僻,一個(gè)開始按鈕糠惫,三個(gè)TextBlock。按一下開始按鈕钉疫,開一個(gè)后臺(tái)線程隨機(jī)得到一個(gè)數(shù)字硼讽,并且更新第一個(gè)TextBlock。再開另外一個(gè)后臺(tái)線程得到另外一個(gè)數(shù)字陌选,更新第二個(gè)TextBlock理郑。第三個(gè)TextBlock處理同理蹄溉。
XAML代碼:
<Window x:Class="UpdateUIDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="130" Width="363">
<Canvas>
<TextBlock Width="40" Canvas.Left="38" Canvas.Top="27" Height="29" x:Name="first" Background="Black" Foreground="White"></TextBlock>
<TextBlock Width="40" Canvas.Left="128" Canvas.Top="27" Height="29" x:Name="second" Background="Black" Foreground="White"></TextBlock>
<TextBlock Width="40" Canvas.Left="211" Canvas.Top="27" Height="29" x:Name="Three" Background="Black" Foreground="White"></TextBlock>
<Button Height="21" Width="50" Canvas.Left="271" Canvas.Top="58" Content="開始" Click="Button_Click"></Button>
</Canvas>
</Window>
后臺(tái)代碼:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(Work);
}
private void Work()
{
Task task = new Task((tb) => Begin(this.first), this.first);
Task task2 = new Task((tb) => Begin(this.second), this.first);
Task task3 = new Task((tb) => Begin(this.Three), this.first);
task.Start();
task.Wait();
task2.Start();
task2.Wait();
task3.Start();
}
private void Begin(TextBlock tb)
{
int i=100000000;
while (i>0)
{
i--;
}
Random random = new Random();
String Num = random.Next(0, 100).ToString();
tb.Text = Num;
}
}
運(yùn)行一下,在點(diǎn)擊開始按鈕的時(shí)候您炉,得到了一個(gè)錯(cuò)誤信息:
果然不出所料柒爵,Begin函數(shù)是在后臺(tái)線程執(zhí)行的,tb這個(gè)TextBlock是前臺(tái)UI線程的對(duì)象赚爵,所以無法在后臺(tái)線程改變UI線程擁有的對(duì)象棉胀,很多有點(diǎn)經(jīng)驗(yàn)的WPF程序員就會(huì)使用下面我要說的Dispatcher了!
問題解決
* 方法一:Dispatcher
1. 把UI更新的代碼放到一個(gè)函數(shù)中:
private void UpdateTb(TextBlock tb, string text)
{
tb.Text = text;
}
2. 使用Dispatcher,大家看修改后的Begin函數(shù)(紅色內(nèi)容):
private void Begin(TextBlock tb)
{
int i=100000000;
while (i>0)
{
i--;
}
Random random = new Random();
String Num = random.Next(0, 100).ToString();
Action<TextBlock, String> updateAction = new Action<TextBlock, string>(UpdateTb);
tb.Dispatcher.BeginInvoke(updateAction,tb,Num);
}
再運(yùn)行一次程序冀膝,可以看到能正常顯示了唁奢,并且不會(huì)出現(xiàn)假死現(xiàn)象。
* 方法二:任務(wù)調(diào)度器(TaskScheduler)
有很多任務(wù)調(diào)度器窝剖,在CLR Var C#中就提出了線程池任務(wù)調(diào)度器麻掸,I/O任務(wù)調(diào)度器,任務(wù)限時(shí)調(diào)度器等赐纱,調(diào)度器的職責(zé)就是負(fù)責(zé)任務(wù)的調(diào)度脊奋,調(diào)節(jié)任務(wù)執(zhí)行。同步上下文任務(wù)調(diào)度器就是該方法二所使用的調(diào)度器疙描,其作用是將所有任務(wù)都調(diào)度給應(yīng)用程序的GUI線程诚隙。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private readonly TaskScheduler _syncContextTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Factory.StartNew(SchedulerWork);
}
private void SchedulerWork()
{
Task.Factory.StartNew(Begin, this.first).Wait();
Task.Factory.StartNew(Begin, this.second).Wait();
Task.Factory.StartNew(Begin, this.Three).Wait();
}
private void Begin(object obj)
{
TextBlock tb = obj as TextBlock;
int i = 100000000;
while (i>0)
{
i--;
}
Random random = new Random();
String Num = random.Next(0,100).ToString();
Task.Factory.StartNew(() => UpdateTb(tb, Num),
new CancellationTokenSource().Token, TaskCreationOptions.None, _syncContextTaskScheduler).Wait();
}
private void UpdateTb(TextBlock tb, string text)
{
tb.Text = text;
}
}
結(jié)果展示:
總結(jié)
任務(wù)調(diào)度器還有很多種,按照自己喜歡的方法來實(shí)現(xiàn)后臺(tái)多線程更新UI起胰。還有任務(wù)調(diào)度器也可以應(yīng)用到Winform中久又。下面提供示例Demo下載。