BackgroundWorker类
async/await特性适合那些需要在后台完成的不相关的小任务,有时候,需要另建一个线程,在后台持续运行以完成某项工作,并不时地与主线程进行通信,BackgroundWorker类就是为此而生。
BackgroundWorker类的主要成员
- 属性
- WorkerReportsProgress 设置后台任务是否可以把它的进度汇报给主线程
- WorkerSupportsCancellation 是否支持从主线程取消
- IsBusy 检查后台任务是否正在运行
- CancelationPending 状态值,布尔类型
- 方法
- RunWorkerAsync() 获取后台线程并执行DoWork事件处理程序
- CancelAsync() 把CancellationPending属性设置为true。DoWork事件处理程序需要检查这个属性来决定是否应该停止处理
- ReportProgress() DoWork工作的时候向主线程汇报进度
- 事件
- DoWork 在后台线程开始的时候触发
- ProgressChanged 在后台任务汇报状态的时候触发
- RunWorkerCompleted 在后台工作线程退出的时候触发
使用示例:
using System.Threading;
using System.Windows;
using System.ComponentModel;
namespace WpfForAsync
{
///
/// MainWindow.xaml 的交互逻辑
///
public partial class MainWindow : Window
{
// 首先需要创建BackgroundWorker对象
BackgroundWorker bgWorker = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
// 设置BackgroundWorker属性
// WorkerReportsProgress设置工作线程为主线程汇报进度
bgWorker.WorkerReportsProgress = true;
// 设置支持从主线程取消工作线程
bgWorker.WorkerSupportsCancellation = true;
// 连接BackgroundWorkder对象的处理程序
//DoWork是必须的
bgWorker.DoWork += BgWorker_DoWork;
//ProgressChanged与RunWorkerCompleted这两个是可选的
bgWorker.ProgressChanged += BgWorker_ProgressChanged;
bgWorker.RunWorkerCompleted += BgWorker_RunWorkerCompleted;
}
private void BgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar.Value = 0;
if (e.Cancelled)
{
MessageBox.Show("Process was cancelled.", "Process Cancelled");
}
else
{
MessageBox.Show("Process completed normally.", "Process Completed");
}
}
private void BgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
///
/// 这里是希望在后台工作的代码
/// 它是在独立的线程中执行的
///
///
///
private void BgWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i <= 10; i++)
{
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
else
{
/// 后台线程通过调用ReportProgress方法与主线程通信。
/// 届时将触发ProgressChanged事件
/// 主线程可以处理附加到ProgressChanged事件上的处理程序
worker.ReportProgress(i * 10);
Thread.Sleep(500);
}
}
}
private void btnProcess_Click(object sender, RoutedEventArgs e)
{
if (!bgWorker.IsBusy)
{
//主线程调用RunWorkerAsync的时候,会触发DoWork事件
bgWorker.RunWorkerAsync();
}
}
private void progressBar_Click(object sender, RoutedEventArgs e)
{
// 通知后台线程退出
// 此时会将后台线程类的CancellationPending属性设置为true
// DoWork事件处理程序会定期检查CancellationPending确定是否要退出
// 如果后台线程退出,则会触发RunWorkerCompleted事件
bgWorker.CancelAsync();
}
}
}
这是一个WPF程序,前端代码与上一节的一样,后端代码使用后台线程处理
这里再来谈一下每个事件处理程序形参中的参数类EventArgs的结构(所包含的属性)
- DoWorkEventArgs
- Argument
- Result
- Cancel
- ProgressChangedEventArgs
- ProgressPercentage
- UserState
- RunWorkerCompletedEventArgs
- Cancelled
- Error
- Result
- UserState
并行循环
这里介绍Parallel.For
循环和Parallel.ForEach
循环,它们都们于System.Threading.Tasks
命名空间下
这两个循环可以使迭代之间彼此独立,并且程序运行在多核处理器的机器上时,能将不同的迭代放在不同的处理器在上并行处理
Parallel.For
方法有很多个重载,这里介绍其中一个
public static ParallelLoopResult For(int fromInclusive, int toExclusive, Action body);
- fromInclusive参数是迭代系列的第一个整数
- toExclusive参数是比迭代系列最后一个索引号大1的整数
- body是接受单个输入参数的委托,body的代码在每一次迭代中执行一次
示例:
using System;
using System.Threading.Tasks; // 必须使用这个命名空间
namespace CodeForAsync
{
class Program
{
static void Main(string[] args)
{
// 从0开始迭代,直到14
// 打印出索引与索引的平方
Parallel.For(0, 15, i =>
Console.WriteLine("Thre Square of {0} is {1}", i, i * i));
Console.ReadKey();
}
}
}
输出:
Thre Square of 0 is 0
Thre Square of 2 is 4
Thre Square of 1 is 1
Thre Square of 4 is 16
Thre Square of 6 is 36
Thre Square of 7 is 49
Thre Square of 9 is 81
Thre Square of 10 is 100
Thre Square of 11 is 121
Thre Square of 12 is 144
Thre Square of 13 is 169
Thre Square of 8 is 64
Thre Square of 14 is 196
Thre Square of 3 is 9
Thre Square of 5 is 25
另一个示例:
using System;
using System.Threading.Tasks;
namespace CodeForAsync
{
class Program
{
static void Main(string[] args)
{
const int maxValues = 50;
int[] squares = new int[maxValues];
Parallel.For(0, maxValues, i => squares[i] = i * i);
for (int i = 0; i < squares.Length; i++)
{
Console.WriteLine(squares[i]);
}
Console.ReadKey();
}
}
}
本便将索引的平方添加到一个数组中,虽然是并行操作,但是数组里的值却是按顺序按序的
Parallel.ForEach示例
Parallel.ForEach同样有许多重载,这里介绍一个简单的
public static ParallelLoopResult ForEach(IEnumerable source, Action body);
- source是被操作的数据源
- body是一个无返回值的委托
using System;
using System.Threading.Tasks;
namespace CodeForAsync
{
class Program
{
static void Main(string[] args)
{
string[] squares = new string[] { "We", "hold", "these", "truths", "to", "be", "self-evident", "that", "all", "men", "are", "create", "equal" };
Parallel.ForEach(squares, i => Console.WriteLine(string.Format("{0} has {1} letters", i, i.Length)));
Console.ReadKey();
}
}
}
输出:
hold has 4 letters
We has 2 letters
to has 2 letters
be has 2 letters
self-evident has 12 letters
all has 3 letters
men has 3 letters
are has 3 letters
create has 6 letters
equal has 5 letters
these has 5 letters
truths has 6 letters
that has 4 letters
可以看出,这两个循环在操作的时候,每次迭代所输出的顺序都不一样,因为他们是并行循环,也就是将多个迭代放在多核上处理
其他异步编程模式
当委托对象被调用时,它调用了它的调用列表中包含的方法
如果委托对象在调用列表中只有一个方法(引用方法),它就可以异步执行这个方法。委托类有两个方法,叫做BeginInvoke和EndInvoke,它们就是用来这么做的
- 当调用委托的BeginInvoke方法,它开始在一个独立线程上执行引用方法,并且立即返回到原始线程。原始线程可以继续,而引用方法会在线程池的线程中并行执行。
- 当程序希望获取已完成的异步方法的结果时,可以检查BeginInvoke返回的IAsyncResult的IsCompleted属性,或调用委托的EndInvoke该来 等等委托完成
- 在等等——完成模式中,在发起了异步方法以及做了一些其他处理之后,原始线程就中断并且等异步方法完成之后再继续
- 在轮询模式中,原始线程定期检查发起的线和是否完成,如果没有则可以继续做一些其他的事情
- 在回调模式中,原始线程一直执行,无需等等或检查发起的线程是否完成。在发起的线和中的引用方法完成之后,发起的线程就会调用回调方法,由回调方法在调用EndInvoke之前处理异步方法的结果
BeginInvoke和EndInvlke
BeginInvoke的使用
- 在使用BeginInvoke时,参数列表中的实际参数组成如下:
- 引用方法需要的参数
- 两个额外的参数——callbak参数和state参数
- BeginInvoke从线程池中获取一个线程并且让引用方法在新的线程中开始运行
- BeginInvoke返回给调用线程一个实现IAsyncResult接口的对象的引用。这个接口引用包含了在线程池线程中运行的异步方法的当前状态,原始线程然后可以继续执行
示例:
using System;
using System.Threading.Tasks;
namespace CodeForAsync
{
delegate long MyDel(int first, int second);
class Program
{
static long Sum(int x, int y)
{
int z = x + y;
return (long)z;
}
static void Main(string[] args)
{
MyDel del = new MyDel(Sum);
IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
// 3, 5是引用方法所需要的参数
// null, null分别表示回调方法与状态没有值
// 此时从线程池获取一个一线程,并且在新的线程上运行Sum方法
// 收集新线程的状态返回给调用者
Console.ReadKey();
}
}
}
EndInvoke的使用
EndInvlke方法用来获取由异步方法调用返回的值,并且线程使用的资源,其特性如下:
- 它接受一个由BeginInvoke方法返回的IAsyncResult对象的引用,并找到它关联的线程
- 如果线程池的线程已经退出,EndInvlke做如下的事情
- 它清理退出线程的状态并且释放其资源
- 它找到引用方法返回的值并且把它作为返回值
- 如果当EndInvlke被调用时线程池的线程仍然在运行,调用线程就会停止并等等,直到清理完毕并返回值。因为EndInvlke是为开户的线程进行清理,所以必须确保对每一个BeginInvlke都调用EndInvlke
- 如果异步方法触发了异步,在调用EndInvlke时会抛出异常
示例:
using System;
using System.Threading.Tasks;
namespace CodeForAsync
{
delegate long MyDel(int first, int second);
class Program
{
static long Sum(int x, int y)
{
int z = x + y;
return (long)z;
}
static void Main(string[] args)
{
MyDel del = new MyDel(Sum);
IAsyncResult iar = del.BeginInvoke(3, 5, null, null);
//必须将IAsyncResult对象作为参数
//如果委托引用的方法有ref和out参数,也必须包含在EndInvoke参数列表中
long result = del.EndInvoke(iar);
Console.ReadKey();
}
}
}
来看一个完整的示例:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CodeForAsync
{
delegate long MyDel(int first, int second);
class Program
{
static long Sum(int x, int y)
{
Console.WriteLine(" Inside Sum");
Thread.Sleep(100);
return x + y;
}
static void Main(string[] args)
{
MyDel del = new MyDel(Sum);
Console.WriteLine("Before BeginInvoke");
IAsyncResult iar = del.BeginInvoke(3, 5, null, null); // 开始异步调用
Console.WriteLine("After BeginInvoke");
Console.WriteLine("Ding Stuff"); // 在这里可以做点其他事情
long result = del.EndInvoke(iar); // 等等结束并获取结果
Console.WriteLine("After EndInvoke: {0}",result);
Console.ReadKey();
}
}
}
输出:
Before BeginInvoke
After BeginInvoke
Ding Stuff
Inside Sum
After EndInvoke: 8
AsyncResult类
- 当调用委托对象的BeginInvoke方法时,系统创建了一个AsyncResult类的对象。然而,它不返回类对象的引用,而是返回对象中包含的IAsyncResult接口的引用
- AsyncResult对象包含一个叫做AsyncDelegate的属性,它返回一个指向被调用来开户异步方法的委托的引用。但是,这个属性是类对象的一部分而不是接口的一部分
- 该类还包含一个IsCompleted属性,返回一个布尔值,表示方法是否完成
- 该类还包含一个AsyncState属性,返回一个对象的引用,作为BeginInvoke方法调用时的state参数。它返回object类型的引用
轮询模式
在轮询模式中,原始线程发起了异步方法的调用,做一些其他处理,然后使用IAsyncResult对象的IsComplete属性来定期检查开户的线程是否完成。如果异步方法已经完成 ,原始线程就调用EenInvoke并继续。否则,它做一些其他处理,然后过一会再检查
示例:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CodeForAsync
{
delegate long MyDel(int first, int second);
class Program
{
static long Sum(int x, int y)
{
Console.WriteLine(" Inside Sum");
Thread.Sleep(100);
return x + y;
}
static void Main(string[] args)
{
MyDel del = new MyDel(Sum);
Console.WriteLine("Before BeginInvoke");
IAsyncResult iar = del.BeginInvoke(3, 5, null, null); // 开始异步调用
Console.WriteLine("After BeginInvoke");
// 轮询
while (!iar.IsCompleted)
{
Console.WriteLine("Not Done");
//继续处理
for (int i = 0; i < 10000; i++) ;
}
Console.WriteLine("Dnoe");
long result = del.EndInvoke(iar); // 等等结束并获取结果
Console.WriteLine("Result: {0}",result);
Console.ReadKey();
}
}
}
回调模式
回调模式中,一旦初始线程发起了异步方法,它就只管自己,不再考虑同步。当异步方法调用结束之后,系统调用一个用户自定义的方法来处理结果,并且调用委托的EndInvoke方法。这个用户自定义的方法叫做回调方法或回调
语法示例:
IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone), null);
// or
IAsyncResult iar = del.BeginInvoke(3, 5, CallWhenDone , null);
从示例中了解真相:
using System;
using System.Runtime.Remoting.Messaging; // 调用AsyncResult类型
using System.Threading;
namespace CodeForAsync
{
delegate long MyDel(int first, int second);
class Program
{
static long Sum(int x, int y)
{
Console.WriteLine(" Inside Sum");
Thread.Sleep(100);
return x + y;
}
///
/// 定义一个方法,用作回调
/// 这里想在回调方法中使用委托对象的EndInvoke来处理异步方法执行后的输出
/// 但是委托对象在主线程里,而非在异步线程里
/// 所以,这里将IAsyncResult对象作为参数
///
///
static void CallWhenDone(IAsyncResult iar)
{
Console.WriteLine(" Inside CallWhenDone");
//将传来的委托对象进行向下转换
//以获取异步状态
AsyncResult ar = (AsyncResult)iar;
MyDel del = (MyDel)ar.AsyncDelegate;
long result = del.EndInvoke(iar);
Console.WriteLine(" Thre result is: {0}", result);
}
static void Main(string[] args)
{
MyDel del = new MyDel(Sum);
Console.WriteLine("Before BeginInvoke");
//state参数接收一个object类型对象
//为了在回调方法中使用委托del,这里将del对象作为状态类传入
IAsyncResult iar = del.BeginInvoke(3, 5, new AsyncCallback(CallWhenDone), del); // 开始异步调用
Console.WriteLine("Doing more work in Main");
Thread.Sleep(500);
Console.WriteLine("Done with Main. Exiting.");
Console.ReadKey();
}
}
}
输出:
Before BeginInvoke
Doing more work in Main
Inside Sum
Inside CallWhenDone
Thre result is: 8
Done with Main. Exiting.
计时器
这里介绍System.Threading命名空间中的那个Timer类
-
计时器在每次时间到期之后调用回调方法。回调方法必须是TimerCallback委托形式的,结构如下所示。它接受了一个object类型作为参数,并且返回类型是void
void TimerCallback(object state)
-
相关属性:
- dueTime:是回调方法首次被调用之前的时间
- period:是两次成功调用回调函数之间的时间间隔
- state: 可以是null或在每次回调方法执行时机传入的对象的引用
Timer常用的构造如下:
Timer(TimerCallback callback, object state, uint dueTime, uint period)
示例:
using System;
using System.Runtime.Remoting.Messaging; // 调用AsyncResult类型
using System.Threading;
namespace CodeForAsync
{
delegate long MyDel(int first, int second);
class Program
{
int TimesCalled = 0;
void Display(object state)
{
Console.WriteLine("{0} {1}",state.ToString(), ++TimesCalled);
}
static void Main(string[] args)
{
Program p = new Program();
//参数分别为:回调方法、状态信息、开始前时间、间隔时间
Timer myTimer = new Timer(p.Display, "Processing timer event", 2000, 1000);
Console.WriteLine("Timer started.");
Console.ReadKey();
}
}
}