WPF QuickStart系列之线程模型(Thread Model)

这篇博客将介绍WPF中的线程模型。

首先我们先来看一个例子,用来计算一定范围内的素数个数。

XAML:

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="">
<TextBlock Text="From:" />
<TextBox Margin="10,2,2,2" Width="" MaxLength="" x:Name="_from"/>
<TextBlock Text="To:" Margin="20,0,0,0"/>
<TextBox Margin="10,2,2,2" Width="" MaxLength="" x:Name="_to"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="" Margin="">
<Button Content="Calculate" Padding="" Click="CalcButtonClick" x:Name="_calcButton"/>
<Button Content="Cancel" Padding="" Margin="10,0,0,0" IsEnabled="False" x:Name="_cancelButton" Click="CancleButtonClick"/>
</StackPanel>
<TextBlock x:Name="_result" Grid.Row="" FontSize="" Margin="" HorizontalAlignment="Center" />
</Grid>

C#:

private void CalcButtonClick(object sender, RoutedEventArgs e)
{
_result.Text = string.Empty; int from = int.Parse(_from.Text);
int to = int.Parse(_to.Text); _calcButton.IsEnabled = false; _cancelButton.IsEnabled = true; int total = CountPrimes(from, to); _result.Text = "Total Primes: " + total.ToString(); _calcButton.IsEnabled = true;
} private int CountPrimes(int from, int to)
{
int total = ; for (int i = from; i <= to; i++)
{
bool isPrime = true;
int limit = (int)Math.Sqrt(i);
for (int j = ; j <= limit; j++)
if (i % j == )
{
isPrime = false;
break;
}
if (isPrime)
total++;
}
return total;
}

运行之后,如果输入的值很大,例如需要查找1到100000000之间的素数。发现一会儿界面就会卡死。这是因为我们的计算方法阻塞了UI线程。此时我们应该考虑将计算方法放在一个线程中去计算。将计算的代码放置在ThreadPool中,不建议使用Thread,因为ThreadPool会帮助我们来管理线程。

修改CalcButtonClick的C#代码如下:

private void CalcButtonClick(object sender, RoutedEventArgs e)
{
_result.Text = string.Empty; int from = int.Parse(_from.Text);
int to = int.Parse(_to.Text); _calcButton.IsEnabled = false; _cancelButton.IsEnabled = true; ThreadPool.QueueUserWorkItem((state) => { int total = CountPrimes(from, to); _result.Text = "Total Primes: " + total.ToString(); _calcButton.IsEnabled = true; });
}

此时查找1到100000000之间的素数,发现界面可以*的拖拽。不过会产生一个异常。截图如下:

WPF QuickStart系列之线程模型(Thread Model)

错误信息告诉我们在一个线程中访问另一个线程创建的对象。这是因为_result这个TextBlock是UI线程创建的。我们不能在一个后台线程中访问它。下面就要引出WPF中专门用于UI同步的Dispatcher对象。继续改进代码,

private void CalcButtonClick(object sender, RoutedEventArgs e)
{
_result.Text = string.Empty; int from = int.Parse(_from.Text);
int to = int.Parse(_to.Text); _calcButton.IsEnabled = false; _cancelButton.IsEnabled = true; ThreadPool.QueueUserWorkItem((state) => { int total = CountPrimes(from, to); Dispatcher.BeginInvoke(new Action(() =>
{
_result.Text = "Total Primes: " + total.ToString(); _calcButton.IsEnabled = true;
}));
});
}

把更新UI的代码都放置在Dispatcher.BeginInvoke中执行。这次我们会看到最终的执行结果。

细心的你会返现Dispatcher对象还有一个Invoke的方法。这两者的区别BeginInvoke是异步更新UI,不会阻塞UI,Invoke方法是同步更新UI,如果需要更新到UI的内容很多,会造成UI的阻塞,建议使用BeginInvoke。

我们还可以使用SynchronizationContext来进行UI同步,代码如下:

private void CalcButtonClick(object sender, RoutedEventArgs e)
{
_result.Text = string.Empty; int from = int.Parse(_from.Text); int to = int.Parse(_to.Text); Button button = sender as Button; button.IsEnabled = false; SynchronizationContext sc = SynchronizationContext.Current; ThreadPool.QueueUserWorkItem((p) => { int total = CountPrimes(from, to); sc.Send(delegate { _result.Text = "Total Primes: " + total.ToString(); button.IsEnabled = true; }, null);
});
}

SynchronizationContext中有两个方法用来做UI同步,SendPost,这两者区别于Dispatcher中BeginInvoke和Invoke是一样的。Send方法是异步进行UI同步,Post方法是同步的方式进行UI同步。

说完了WPF中UI线程同步,下面我们看一下如何取消正在进行中的线程操作。

使用CancellationTokenSource类,代码如下:

CancellationTokenSource source = null;

        public MainWindow()
{
InitializeComponent();
} private void CalcButtonClick(object sender, RoutedEventArgs e)
{
source = new CancellationTokenSource(); _result.Text = string.Empty; int from = int.Parse(_from.Text); int to = int.Parse(_to.Text); Button button = sender as Button; button.IsEnabled = false; SynchronizationContext sc = SynchronizationContext.Current; ThreadPool.QueueUserWorkItem((p) => { int total = CountPrimes(from, to,source.Token); sc.Send(delegate { _result.Text = total > ? "Total Primes: " + total.ToString() : "Canceled."; button.IsEnabled = true; }, null); }); } private int CountPrimes(int from, int to, CancellationToken token)
{
int total = ;
for (int i = from; i <= to; i++)
{
if(token.IsCancellationRequested)
{
return -;
} bool isPrime = true;
int limit = (int)Math.Sqrt(i);
for (int j = ; j <= limit; j++)
if (i % j == )
{
isPrime = false;
break;
}
if (isPrime)
total++;
}
return total;
} private void CancelButtonClick(object sender, RoutedEventArgs e)
{
if(source != null)
{
source.Cancel();
}
}

至此,我们将WPF中后台线程与UI的同步写完了。还有一个重要部分,就是将执行的进度在客户端展示,这样可以让用户清楚的知道目前执行的进度,而不是一直等待,增强了用户体验。可以使用BackgroudWorker来实现。在此就不再赘述。

感谢您的阅读,如果您对博客中内容有疑问,欢迎在评论中指出来。

上一篇:oracle返回多个参数


下一篇:Linux comands