C#中的线程一

二、异步委托

//异步委托
public static void Start2()
{
    Console.WriteLine("main thread:{0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
    //DoSomethingDelegate del = new DoSomethingDelegate(Method1);
    DoSomethingDelegate del = Method1;
    del.BeginInvoke("this is delegate method", null, null);
    Console.WriteLine("main thread other things...");
}

此次我们利用委托的BeginInvoke方法进行方法调用,BeginInvoke的方法签名如下:

IAsyncResult  DoSomethingDelegate.BeginInvoke(string name,AsyncCallBack  callback,object @object)

那么利用BeginInvoke进行方法调用的结果如何呢?如结果显示,BeginInvoke调用的方法有一个子线程去调用,主线程没有被执行到,Thread.Sleep(TimeSpan.FromSeconds(3));这个方法,也就没有被挂起线程。

永利官网ylg客户端 1

引言

如果你看过了 C#中的委托和事件 一文,我想你对委托和事件已经有了一个基本的认识。但那些远不是委托和事件的全部内容,还有很多的地方没有涉及。本文将讨论委托和事件一些更为细节的问题,包括一些大家常问到的问题,以及事件访问器、异常处理、超时处理和异步方法调用等内容。

一、同步委托

我们平时所用的委托以同步居多,我们编写一个方法和相关委托进行演示:

publicdelegatevoid DoSomethingDelegate(string name);
 //同步委托
public static void Start1()
{
    Console.WriteLine("this is primary thread");
    Console.WriteLine("main thread:{0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
    //DoSomethingDelegate del = new DoSomethingDelegate(Method1);
    //注意这里,简单起见还可以把一个方法名直接赋给一个委托类型
     DoSomethingDelegate del = Method1;
     del("this is delegate method");
 }
//委托所关联的方法
 public static void Method1(string name)
 {
     Console.WriteLine("sub thread:           {0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
     Console.WriteLine(name);
     Thread.Sleep(TimeSpan.FromSeconds(3));
     Console.WriteLine("sub thread other things...");
  }

我们分析下这个Start1()方法,首先显示了主线程相关的信息,然后定义了一个委托类型del,利用del(“this
is delegate method”)执行Method1(string
name)方法,由于是同步委托,所以主线程在执行到Thread.Sleep(TimeSpan.FromSeconds(3));处会暂时挂起,3秒后才继续执行,然后才返回到Start1()方法中继续执行。

我们运行Start1()方法后看看执行顺序

永利官网ylg客户端 2

可以看到,运行结果是按主线程的执行顺序依次往下执行。

来源

三、异步委托详解

刚才我们通过del.BeginInvoke(“this is delegate method”, null,
null);这样就做到了异步调用,我们在编写代码中还有这样一种需求,如果你要进行异步调用,子线程执行的结果怎么返回给主线程呢?del.EndInvoke上场了!

//异步委托得到返回值,实际上为了得到返回值,阻碍了主线程
 public static void Start3()
   {
     Console.WriteLine("main thread:{0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
      //DoSomethingDelegate del = new DoSomethingDelegate(Method1);
     DoSomethingDelegate2 del = Method2;
     IAsyncResult result=del.BeginInvoke("this is delegate method",null,null);
     string s = del.EndInvoke(result);
     Console.WriteLine("得到返回值:" + s);
     Console.WriteLine("main thread other things...");
   }//异步委托所调用的方法,注意此方法有返回值 
 public static string  Method2(string name)
   {
       Console.WriteLine("sub thread:{0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
       Console.WriteLine(name);
       Thread.Sleep(TimeSpan.FromSeconds(3));
       Console.WriteLine("sub thread other things...");
       return "返回委托值";
   }

从实例代码中我们可以看到,我们为了得到异步方法的返回值写了这么两行代码:

IAsyncResult result=del.BeginInvoke("this is delegate method",null,null);
string s = del.EndInvoke(result);

永利官网ylg客户端 3

我们查看执行结果:由运行结果可以看到,屏幕输出了返回值,但是Method2(string
name)方法并没有被异步执行到!原因在于string s =
del.EndInvoke(result);这句阻碍了主线程的继续执行,等子线程返回值后赋给s后,主线程才继续执行。这样写的后果就是:为了得到返回值,阻碍了主线程

我们刚才执行异步委托都是通过下面的代码来完成的

IAsyncResult result=del.BeginInvoke("this is delegate method",null,null);

我们将BeginInvoke方法的第二个和第三个参数都设置为了null,我们现在来看看这两个参数的作用!第二个参数AsyncCallBack 
callback,这个参数实际上是一个回调委托,我们看此委托的定义:

public delegate void AsyncCallback(IAsyncResult ar);

什么是回调方法?就是说委托所调用的方法执行完毕后自动执行的方法,即上面的Method2(string
name)方法被异步执行结束后所调用的方法。于是我们在定义一个跟AsyncCallback委托匹配的方法:

public static void CallBack(IAsyncResult result)
  {
    DoSomethingDelegate2 del = result.AsyncState as DoSomethingDelegate2;
    string s = del.EndInvoke(result);
    Console.WriteLine("得到返回值:" + s);
  }

public static void Start4()
 {
    Console.WriteLine("main thread:{0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
    DoSomethingDelegate2 del = Method2;
    AsyncCallback callBack = CallBack;
     del.BeginInvoke("this is delegate method", callBack, del);
    Console.WriteLine("main thread other things...");
  }

public static string  Method2(string name)
  {
     Console.WriteLine("sub thread:{0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
     Console.WriteLine(name);
     Thread.Sleep(TimeSpan.FromSeconds(3));
     Console.WriteLine("sub thread other things...");
     return "返回委托值";
  }

从上面的代码可以看出,在CallBack方法中我们得到了Method2(string
name)方法的返回值。并且整个过程是异步执行的!请看运行结果:

永利官网ylg客户端 4

为了得到异步方法的返回值还可以这么做:

public static void Start4()
   {
      Console.WriteLine("main thread:{0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId);
      DoSomethingDelegate2 del = Method2;
      //另一种实现方法
      del.BeginInvoke("this is delegate method", CallBack2, null);
      Console.WriteLine("main thread other things...");
   }

public static void CallBack2(IAsyncResult result)
 {
    AsyncResult ar = result as AsyncResult;
    DoSomethingDelegate2 del = ar.AsyncDelegate as DoSomethingDelegate2;
    string s=del.EndInvoke(ar);
    Console.WriteLine("得到返回值:" + s);
 }

这段代码的运行效果跟上面是一样的,只不过写法不同而已!

为什么委托定义的返回值通常都为void?

尽管并非必需,但是我们发现很多的委托定义返回值都为void,为什么呢?这是因为委托变量可以供多个订阅者注册,如果定义了返回值,那么多个订阅者的方法都会向发布者返回数值,结果就是后面一个返回的方法值将前面的返回值覆盖掉了,因此,实际上只能获得最后一个方法调用的返回值。可以运行下面的代码测试一下。除此以外,发布者和订阅者是松耦合的,发布者根本不关心谁订阅了它的事件、为什么要订阅,更别说订阅者的返回值了,所以返回订阅者的方法返回值大多数情况下根本没有必要。

class Program {
    static void Main(string[] args) {
        Publishser pub = new Publishser();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();
        Subscriber3 sub3 = new Subscriber3();

        pub.NumberChanged += new
GeneralEventHandler(sub1.OnNumberChanged);
        pub.NumberChanged += new
GeneralEventHandler(sub2.OnNumberChanged);
        pub.NumberChanged += new
GeneralEventHandler(sub3.OnNumberChanged);
        pub.DoSomething();          // 触发事件
    }
}

// 定义委托
public delegate string GeneralEventHandler();

// 定义事件发布者
public class Publishser {
    public event GeneralEventHandler NumberChanged; // 声明一个事件
    public void DoSomething() {
        if (NumberChanged != null) {    // 触发事件
            string rtn = NumberChanged();
            Console.WriteLine(rtn);     //
打印返回的字符串,输出为Subscriber3
        }
    }
}

// 定义事件订阅者
public class Subscriber1 {  
    public string OnNumberChanged() {
        return “Subscriber1”;
    }
}
public class Subscriber2 { /* 略,与上类似,返回Subscriber2*/ }
public class Subscriber3 { /* 略,与上类似,返回Subscriber3*/ }

如果运行这段代码,得到的输出是Subscriber3,可以看到,只得到了最后一个注册方法的返回值。

如何让事件只允许一个客户订阅?

少数情况下,比如像上面,为了避免发生“值覆盖”的情况(更多是在异步调用方法时,后面会讨论),我们可能想限制只允许一个客户端注册。此时怎么做呢?我们可以向下面这样,将事件声明为private的,然后提供两个方法来进行注册和取消注册:

// 定义事件发布者
public class Publishser {
    private event GeneralEventHandler NumberChanged;    //
声明一个私有事件
    // 注册事件
    public void Register(GeneralEventHandler method) {
        NumberChanged = method;
    }
    // 取消注册
    public void UnRegister(GeneralEventHandler method) {
        NumberChanged -= method;
    }

    public void DoSomething() {
        // 做某些其余的事情
        if (NumberChanged != null) {    // 触发事件
            string rtn = NumberChanged();
            Console.WriteLine(“Return: {0}”, rtn);      //
打印返回的字符串,输出为Subscriber3
        }
    }
}

NOTE:注意上面,在UnRegister()中,没有进行任何判断就使用了NumberChanged-=method语句。这是因为即使method方法没有进行过注册,此行语句也不会有任何问题,不会抛出异常,仅仅是不会产生任何效果而已。

注意在Register()方法中,我们使用了赋值操作符“=”,而非“+=”,通过这种方式就避免了多个方法注册。上面的代码尽管可以完成我们的需要,但是此时大家还应该注意下面两点:

1、将NumberChanged声明为委托变量还是事件都无所谓了,因为它是私有的,即便将它声明为一个委托变量,客户端也看不到它,也就无法通过它来触发事件、调用订阅者的方法。而只能通过Register()和UnRegister()方法来注册和取消注册,通过调用DoSomething()方法触发事件(而不是NumberChanged本身,这在前面已经讨论过了)。

2、我们还应该发现,这里采用的、对NumberChanged委托变量的访问模式和C#中的属性是多么类似啊?大家知道,在C#中通常一个属性对应一个类型成员,而在类型的外部对成员的操作全部通过属性来完成。尽管这里对委托变量的处理是类似的效果,但却使用了两个方法来进行模拟,有没有办法像使用属性一样来完成上面的例子呢?答案是有的,C#中提供了一种叫事件访问器(Event
Accessor)的东西,它用来封装委托变量。如下面例子所示:

class Program {
    static void Main(string[] args) {
        Publishser pub = new Publishser();
        Subscriber1 sub1 = new Subscriber1();
        Subscriber2 sub2 = new Subscriber2();

        pub.NumberChanged -= sub1.OnNumberChanged;  // 不会有任何反应
        pub.NumberChanged += sub2.OnNumberChanged;  // 注册了sub2
        pub.NumberChanged += sub1.OnNumberChanged;  //
sub1将sub2的覆盖掉了
        
        pub.DoSomething();          // 触发事件
    }
}

// 定义委托
public delegate string GeneralEventHandler();

// 定义事件发布者
public class Publishser {
    // 声明一个委托变量
    private GeneralEventHandler numberChanged;
    // 事件访问器的定义
    public event GeneralEventHandler NumberChanged {
        add {
            numberChanged = value;
        }
永利官网ylg客户端,        remove {
            numberChanged -= value;
        }
    }
    
    public void DoSomething() {
        // 做某些其他的事情
        if (numberChanged != null) {    // 通过委托变量触发事件
            string rtn = numberChanged();
            Console.WriteLine(“Return: {0}”, rtn);      //
打印返回的字符串
        }
    }
}

// 定义事件订阅者
public class Subscriber1 {
    public string OnNumberChanged() {
        Console.WriteLine(“Subscriber1 Invoked!”);
        return “Subscriber1”;
    }
}
public class Subscriber2 {/* 与上类同,略 */}
public class Subscriber3 {/* 与上类同,略 */}

上面代码中类似属性的public event GeneralEventHandler NumberChanged
{add{…}remove{…}}语句便是事件访问器。使用了事件访问器以后,在DoSomething方法中便只能通过numberChanged委托变量来触发事件,而不能NumberChanged事件访问器(注意它们的大小写不同)触发,它只用于注册和取消注册。下面是代码输出:

Subscriber1 Invoked!
Return: Subscriber1

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注