现在的位置: 首页 > 综合 > 正文

C#异步调用

2013年05月16日 ⁄ 综合 ⁄ 共 4408字 ⁄ 字号 评论关闭

  在做一个winform打印条码应用的时候碰到一个问题,在条码打印方法中循环打印,由于需要打印的数量比较多,

这时候就导致当点击打印的时候整个应用程序就死在那里不动了,一直到打印结束之后才会有反应.

查了点资料,下面是异步调用的方法:

  1. 通过异步调用,在打印方法中通过委托调用执行时间比较长的方法

    假如需要循环打印的方法是这样的:

public string Print(int Num){
//这里循环打印,Num为打印数量,打印完成后返回打印成功的提示信息
}

    (1). 首先要建立一个代表打印的委托,以便这个委托可以异步调用打印方法

//委托与需要代表的方法具有相同的参数和返回类型
public delegate string PrintDel(int Num);

    (2). 在打印按钮的事件中初始化委托并开始异步调用,BeginInvoke会将自身结果(IAsyncResult类型)当作参数传递给它的回调函数,而且BeginInvoke的最后一个参数将作为ar.AsyncState(Object类型)传递给回调函数

PrintDel del=new PrintDel(Print);
IAsyncResult ar = del.BeginInvoke(100,new AsyncCallback(CallbackMethod),del);
//这里BeginInvoke有几个参数,前面的参数是和所要代理的打印方法一致的参数,最后2个参数:一个是表明
当异步调用结束时回调哪个方法(CallbackMethod),最后一个是回调函数将要用到的信息,这里直接传递代理
对象PrintDel del,作用是在回调函数中可以让这个del结束异步调用,即del.EndInvoke()

    (3). 在回调方法中执行打印完成后的操作(显示打印成功的信息和其他操作)

void CallbackMethod(IAsyncResult ar){
PrintDel del=(PrintDel)ar.AsyncState;
string msg=del.EndInvoke(ar);
//这里msg(即EndInvoke的结果)就是打印完成后返回的信息
}

  2. winform中异步调用可能出现的问题

    异步调用相当于在主线程中开辟了一个新的线程在后台来执行需要异步调用的方法,在这个方法中如果要操作其他控件(比如将文本框填入某一字符串),就会抛出一个cross-thread异常,这个异常是由于编译器出于安全考虑,不允许不是创建这个控件的线程来调用这个控件(文本框).解决的方法是:

    在这个另外的一个线程中通过使用这个控件的Invoke(Delegate method,params object[] args)方法,Invoke可以调用一个委托,可以在这个委托所代表的方法中对这个控件进行操作;

    Invoke有2个参数,第一个是要执行操作的方法的代理,第二个是要执行操作的方法需要的参数对象数组.

    下面是一个小例子(在按钮事件新建立的线程中对一个文本框写入"helloworld"字符串):

        delegate void AddMsgDel(string msg);
private void button1_Click(object sender, EventArgs e)
{

Thread thread = new Thread(new ThreadStart(Method));
thread.Start();
}
private void Method() {
AddMsgDel del=new AddMsgDel(AddMsg);
textBox1.Invoke(del, new object[] { "helloworld" });
}
public void AddMsg(string msg) {
this.textBox1.Text = msg;
}

    或者利用EventHandler委托,这样就省去自己建立委托的麻烦了:

        private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(new ThreadStart(Method));
thread.Start();
}
private void Method() {
this.textBox1.Invoke(new EventHandler(AddMsg));
}
public void AddMsg(object sender, EventArgs e)
{
this.textBox1.Text = "helloworld";
}

    需要注意的是,EventHandler这个委托只代表具有参数(object sender,EventArgs)的方法。

*************************************************************************************************************************************************

    如果需要通过其它线程频繁调用某个主线程的控件,给这个控件设置属性的操作,可以将设置属性的方法封装成一个函数,在函数里面判断这个空间是否需要调用Invoke(InvokeRequired):

        //下面是跨線程給一個PictureBox控件设置一个图片的方法:

delegate void CallBackPictureBox(PictureBox tc, string path);

private void SetPictureBox(PictureBox tc, string path)

{

try

{

if (tc.InvokeRequired)

{

CallBackPictureBox d = new CallBackPictureBox(SetPictureBox);

this.Invoke(d, new object[] { tc, "picture\\" + path });

}

else

{

tc.ImageLocation = path;

tc.Load();

}

}

catch (Exception ex)

{

MessageBox.Show(ex.Message);

}

}

*************************************************************************************************************************************************

 

  3. 利用ManualResetEvent来来控制异步调用的打印的线程的暂停和恢复

    打印过程可能很长,这时候有可能需要暂停下来做一些事情,然后回来继续接着打印

    打印过程中有2个线程:一个是程序运行的主线程,一个是异步调用的打印线程,需要实现的是在主线程中的控件(暂停打印按纽)来控制打印线程

                的暂停和恢复。

    ManualResetEvent就像一个信号灯,当有信号的时候(初始化为true或者有线程调用它的set()方法)就表示所有在等待(WaitOne())的线程,你们可以

    继续运行了,当没有信号的时候(初始化为false或者有线程调用它的Reset()方法)就表示所有在等待的线程,你们继续等着      

    下面是例子代码:

//程序开始的时候设置为有信号,这样当点击打印按纽的时候,就可以直接打印
static ManualResetEvent mre = new ManualResetEvent(true);
private void btnPrint_Click(object sender, EventArgs e)
{
//在打印按纽事件中异步调用打印方法,循环打印
//在打印循环体中有一个mre.WaitOne();当信号灯有信号的时候就继续
//当信号灯没有信号的时候就暂停
}

//在控制暂停和继续的按纽事件中通过设置信号灯有和无来控制继续打印还是暂停打印
private void btnStopPrint_Click(object sender, EventArgs e)
{
if (btnStopPrint.Text == "暫停打印") {
mre.Reset();
btnStopPrint.Text = "繼續打印";
}
else if (btnStopPrint.Text == "繼續打印") {
mre.Set();
btnStopPrint.Text = "暫停打印";
}
}

    还有一个AotoResetEvent和ManualResetEvent类似,只是一个是自动,一个是手动。AotoResetEvent只允许一个线程获得信号,

    当一个线程获得该信号之后AotoResetEvent就会自动Reset()掉置为无信号

  4. .net中关于异步调用有2中机制:同步对象,回调机制

    这部分参考深入学习Web Service系列之异步开发模式

    以上打印的例子属于回调机制,还有一种是同步对象机制。在同步对象的情况下,主线程不会执行回调处理,在开始异步调用(Begin)之后,会继续执行其它操作,在调用End方法的时候,给End方法传递同步对象,得到调用结果(所谓的同步对象应该就是Begin方法的返回值吧,比如IAsyncResult ar)。同步对象在执行异步代码的时候会阻塞主线程。

    示例代码(ar.AsyncWaitHandle.WaitOne()可以让当前线程挂起,直到End调用结束才会被唤醒,wsc是WebService):

 

 1/// <summary>
 2/// 利用同步对象实现异步调用
 3/// </summary>
 4/// <param name="sender"></param>
 5/// <param name="e"></param>

 6private void btn_AsyncClient_Click(object sender, System.EventArgs e)
 7{
 8    IAsyncResult ar = wsc.BeginHello(this.txt_UserName.Text, nullnull);
 9
10
11    MessageBox.Show("Continue to do some other things");
12
13    ar.AsyncWaitHandle.WaitOne();
14
15    strHello = wsc.EndHello(ar);
16
17    this.rtb_Result.Text = strHello;
18}

    关于异步调用有好几种情况,参照:  C#异步调用四大方法

 

 

抱歉!评论已关闭.