一、线程概述:【引用MSDN】
通常,WPF 应用程序从两个线程开始:一个用于处理呈现,一个用于管理 UI。呈现线程有效地隐藏在后台运行,而 UI 线程则接收输入、处理事件、绘制屏幕以及运行应用程序代码。大多数应用程序都使用一个 UI 线程,但在某些情况下,最好使用多个线程。我们将在后面举例说明这一点。
UI 线程对一个名为 Dispatcher 的对象内的工作项进行排队。Dispatcher 基于优先级选择工作项,并运行每一个工作项,直到完成。每个 UI 线程都必须至少有一个 Dispatcher,并且每个Dispatcher 都只能在一个线程中执行工作项。
要构建响应速度快、且用户友好的应用程序,诀窍是减小工作项,以最大限度地提高Dispatcher 吞吐量。这样,工作项将永远不会因为在Dispatcher 队列中等待处理而失效。输入与响应之间的任何可察觉的延迟都会使用户不快。
那么,WPF 应用程序应如何处理大型操作呢?如果您的代码涉及大型计算,或者需要查询某台远程服务器上的数据库,应怎么办呢?通常的办法是在单独的线程中处理大型操作,而专门让 UI 线程来负责处理Dispatcher 队列中的工作项。当大型操作完成时,可以将结果报告给 UI 线程来显示。
一直以来,Windows 只允许创建 UI 元素的线程访问这些元素。这意味着负责某项长时间运行任务的后台线程无法更新已完成的文本框。Windows 这样做是为了确保 UI 组件的完整性。如果列表框的内容在绘制过程中被后台线程更新,那么该列表框看上去将会很奇怪。
WPF 使用一种内置互斥机制来强制执行这种协调。WPF 中的大多数类都派生自DispatcherObject。DispatcherObject 在构造时存储对链接到当前所运行线程的 Dispatcher 的引用。实际上,DispatcherObject 与创建它的线程关联。在程序执行过程中,DispatcherObject 可以调用它的公共VerifyAccess 方法。VerifyAccess 检查与当前线程关联的Dispatcher,并将它与构造过程中存储的Dispatcher 引用进行比较。如果两者不匹配,VerifyAccess 将引发异常。VerifyAccess 用于在每个属于DispatcherObject 的方法的开头调用。
如果只有一个线程可以修改 UI,那么后台线程如何与用户交互呢?后台线程可以请求 UI 线程代表它执行操作。这是通过向 UI 线程的Dispatcher 注册工作项来完成的。Dispatcher 类提供两个注册工作项的方法:Invoke 和BeginInvoke。这两个方法均调度一个委托来执行。Invoke 是同步调用,也就是说,直到 UI 线程实际执行完该委托它才返回。BeginInvoke 是异步的,将立即返回。
Dispatcher 按优先级对其队列中的元素进行排序。向Dispatcher 队列中添加元素时可指定 10 个级别。这些优先级在DispatcherPriority 枚举中维护。有关DispatcherPriority 级别的详细信息可以在 Windows SDK 文档中找到。
二、下面是线程的一个小例子
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" VerticalAlignment="Center" >
<Button Content="StartWithThread"
Click="StartOrStop"
Name="startStopButton"
Margin="5,0,5,0"
/>
<TextBlock Margin="10,5,0,0">Biggest Prime Found:</TextBlock>
<TextBlock Name="bigPrime" Margin="4,5,0,0">3</TextBlock>
</StackPanel>
<StackPanel Grid.Row="1" Orientation="Horizontal" VerticalAlignment="Center" >
<Button Content="MessageBox.show"
Name="startStopButton2"
Margin="5,0,5,0" Click="startStopButton2_Click" />
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal" VerticalAlignment="Center" >
<Button Content="StartWithoutThread"
Name="startStopButton3"
Margin="5,0,5,0" Click="startStopButton3_Click" />
<TextBlock Margin="10,5,0,0">data:</TextBlock>
<TextBlock Name="myData" Margin="4,5,0,0">3</TextBlock>
</StackPanel>
</Grid>
后台代码:
/// <summary>
/// Interaction logic for ThreadTest.xaml
/// </summary>
public partial class ThreadTest : Window
{
public delegate void NextPrimeDelegate();
//Current number to check
private long num = 3;
private bool continueCalculating = false;
public ThreadTest()
{
InitializeComponent();
}
private void StartOrStop(object sender, RoutedEventArgs e)
{
if (continueCalculating)
{
continueCalculating = false;
startStopButton.Content = "Resume";
}
else
{
continueCalculating = true;
startStopButton.Content = "Stop";
startStopButton.Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new NextPrimeDelegate(CheckNextNumber));
}
}
public void CheckNextNumber()
{
// Reset flag.
NotAPrime = false;
for (long i = 3; i <= Math.Sqrt(num); i++)
{
if (num % i == 0)
{
// Set not a prime flag to true.
NotAPrime = true;
break;
}
}
// If a prime number.
if (!NotAPrime)
{
bigPrime.Text = num.ToString();
}
num += 2;
if (continueCalculating)
{
startStopButton.Dispatcher.BeginInvoke(
System.Windows.Threading.DispatcherPriority.SystemIdle,
new NextPrimeDelegate(this.CheckNextNumber));
}
}
private bool NotAPrime = false;
private void startStopButton2_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Hello Thread");
}
private void startStopButton3_Click(object sender, RoutedEventArgs e)
{
long n = 0;
// If a prime number.
while (n < 10000000)
{
myData.Text = n.ToString();
n++;
}
}
}
效果图1:当运行"Stop"按钮时,在单击“MessageBox.show"按钮,则能弹出窗口HelloThead。
效果图2。当单击“StartWithoutThrad"按钮时,此时再单击“MessageBox.show"按钮,则提示Not Responding。因为此时没有用到线程。
参考资料:http://msdn.microsoft.com/zh-cn/library/ms741870.aspx#threading_overview
三、 下面是使用线程的三种方式
第1种用 Task类. 推荐用这个办法
public void 工作_Task()
{
Dispatcher x = Dispatcher.CurrentDispatcher;//取得当前工作线程
//另开线程工作
Task<int> 计数 = new Task<int>(() => { return 计数方法(); });
计数.ContinueWith(工作完毕后方法);//工作完毕后执行的方法
计数.Start();//开始工作
}
public void工作完毕后方法(Task<int> 参数)
{
if (参数.IsCompleted) //正常工作完毕
{
var 结果 = 参数.Result; //取得结果
//处理结果.
//本方法非界面线程.如果需要在界面线程操作,需要转移到界面线程
}
}
int c;
public int 计数方法()
{
return c++;
}
第2种方法用线程
public void 工作_Thread()
{
Dispatcher x = Dispatcher.CurrentDispatcher;//取得当前工作线程
//另开线程工作
System.Threading.ThreadStart start = delegate()
{
//工作函数
Func<string> fu = new Func<string>(() => { return ""; });//工作函数
var 工作结果 = fu();//开始工作
//异步更新界面
x.BeginInvoke(new Action(() =>
{
//在界面线程操作 可以使用 工作结果
}), DispatcherPriority.Normal);
};
new System.Threading.Thread(start).Start(); //启动线程
}
第3种方法用 BackgroundWorker.
这种方法介绍的比较多了.就不说了.
BackgroundWorker 后台线程;
public void线程初始化()
{
后台线程 = new BackgroundWorker();
后台线程.WorkerSupportsCancellation = true; //可以取消
后台线程.DoWork += new DoWorkEventHandler(后台线程_DoWork);
后台线程.RunWorkerCompleted += new RunWorkerCompletedEventHandler(后台线程_RunWorkerCompleted);
}
public void 启动后台线程()
{
后台线程.RunWorkerAsync();
}
void 后台线程_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//工作完毕的方法
}
void 后台线程_DoWork(object sender, DoWorkEventArgs e)
{
//工作方法
}