C# 异步线程调用方法 实现

1:使用线程Thread调用。

//实例化类 起始我们也可以调用同一个类中的函数 这位师傅就是这么演示一下

ThreadTest test = new ThreadTest();

//创建thread类 构造函数的参数为我们的目标函数

Thread thread1 = new Thread(test.Func2);

//开始执行异步任务

thread1.Start();

例如:在添加3个子线程,每个子线程

C# 异步线程调用方法 实现

C# 异步线程调用方法 实现

 

2:async/await 异步

一下代码:注意红色字体,就是异步的关键字

Stopwatch sw = new Stopwatch();

 1: private async Task<int> CountCharacters(int id, string uristring) {

      WebClient wc1 = new WebClient();
        Console.WriteLine("String call {0}      :   {1, 4} ms", id, sw.Elapsed.TotalMilliseconds);  // 
        string result =  await  wc1.DownloadStringTaskAsync(new Uri(uristring));
        Console.WriteLine("call {0} complete:   {1, 4} ms", id, sw.Elapsed.TotalMilliseconds);
        return result.Length;

}

2:调用这个异步方法:

static void Main(string[] args) {

       Task <int> t1 = CountCharacters(1, "http://www.baidu.com");

 }

async/await 特性

C#中使用async/await特性创建异步方法。这个特性结构包括三个部分:  


调用方法  

 public void DoRun()
{
...
Task <int> t1 = CountCharacters(1, "http://www.baidu.com");
Task<int> t2 = CountCharacters(2, "https://www.jd.com");
...

 

调用方法调用异步方法,在异步方法(可能在相同的线程,可能在新的线程)执行任务的时候继续执行自身任务。
异步方法

private async  Task<int> CountCharacters(int id, string uristring)
{
   ...
string result =  await  wc1.DownloadStringTaskAsync(new Uri(uristring));
...
return result.Length;
}
 

异步方法异步执行方法内的任务,进入到异步执行的任务处,立即返回到调用方法
await 表达式 
await表达式指出需要异步执行的任务,异步方法也是到该处返回到调用方法,异步任务执行的时候,调用方法继续执行自身任务。异步方法中至少包含一个await表达式。


关于异步方法的说明:  


方法签名中包含async关键字 
async在方法返回值前,该关键字用以说明方法中包含至少一个await表达式,该关键字并不能创建任何异步任务。
异步方法具备三种返回类型: void、Task、Task

调用方法与异步方法之间的控制流

调用方法与异步方法之间的控制流图:  

 
异步方法中可能包含多个await语句,当最后一个await语句所创建的异步任务执行完毕,Task的属性和返回值设置完成,await语句后的代码在异步方法内同步执行完成,异步方法执行完成并退出。
 

class Program
{
    static void Main(string[] args)
    {
        CancellationTokenSource cts=new CancellationTokenSource();
        CancellationToken token = cts.Token;
        MyClass mc=new MyClass();
        Task t=mc.RunTask(token);
        Console.WriteLine("Async Action");
        Thread.Sleep(3000);
        cts.Cancel();
        t.Wait();
        Console.WriteLine("Was Canceled:{0}",token.IsCancellationRequested);
        Console.ReadKey();
    }
}
class MyClass
{
    private Stopwatch sw=new Stopwatch();
    public async Task RunTask(CancellationToken ct)
    {
        if (ct.IsCancellationRequested)
            return;
        await Task.Run(()=>CycleMethod(ct),ct);
    }
    void CycleMethod(CancellationToken ct)
    {
        sw.Start();
        Console.WriteLine("Starting CycleMethod at time {0,4}",sw.Elapsed.TotalMilliseconds);
        const int max = 5;
        for (int i = 0; i < max; i++)
        {
            if (ct.IsCancellationRequested)
                return;
            Thread.Sleep(1000);
            Console.WriteLine("{0} of {1} iterations completed at time {2,4}",i,max,sw.Elapsed.TotalMilliseconds);
        }
    }
}  
 

3:通过QueueUserWorkItem启动工作者线程 

注意该线程是无返回值的,如果需要有返回值的就不能使用该方法

ThreadPool线程池中有两个重载的静态方法可以直接启动工作者线程:

  •   ThreadPool.QueueUserWorkItem(waitCallback);
  •   ThreadPool.QueueUserWorkItem(waitCallback,Object);

  先把WaitCallback委托指向一个带有Object参数的无返回值方法,再使用ThreadPool.QueueUserWorkItem(WaitCallback)就可以一步启动此方法,此时异步方法的参数被视为null。

  下面来试下用QueueUserWorkItem启动线程池里的一个线程。注意哦,由于是一直存在于线程池,所以不用new Thread()。

 

    class Program
    {
        static void Main(string[] args)
        {
            //工作者线程最大数目,I/O线程的最大数目
            ThreadPool.SetMaxThreads(1000, 1000);   
            //启动工作者线程
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunWorkerThread));

            Console.ReadKey();
        }

        static void RunWorkerThread(object state)
        {
            Console.WriteLine("RunWorkerThread开始工作");
            Console.WriteLine("工作者线程启动成功!");
        }
    }

 

  输出:

  C# 异步线程调用方法 实现

  使用第二个重载方法ThreadPool.QueueUserWorkItem(WaitCallback,object)方法可以把object对象作为参数传送到回调函数中。

 

    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person(1,"刘备");
            //启动工作者线程
            ThreadPool.QueueUserWorkItem(new WaitCallback(RunWorkerThread), p);
            Console.ReadKey();
        }

        static void RunWorkerThread(object obj)
        {
            Thread.Sleep(200);
            Console.WriteLine("线程池线程开始!");
            Person p = obj as Person;
            Console.WriteLine(p.Name);
        }
    }

    public class Person
    {
        public Person(int id,string name) { Id = id; Name = name; }
        public int Id { get; set; }
        public string Name { get; set; }
    }

 

  输出结果如下:

  C# 异步线程调用方法 实现

  通过ThreadPool.QueueUserWork启动工作者线程非常方便,但是WaitCallback委托指向的必须是一个带有object参数的无返回值方法。所以这个方法启动的工作者线程仅仅适合于带单个参数和无返回值的情况。

  那么如果要传递多个参数和要有返回值又应该怎么办呢?那就只有通过委托了。

 

4:BeginInvoke与EndInvoke委托异步调用线程

异步调用委托的步骤如下:

  1. 建立一个委托对象,通过IAsyncResult BeginInvoke(string name,AsyncCallback callback,object state)异步调用委托方法,BeginInvoke方法除最后的两个参数外,其他参数都是与方法参数相对应的。
  2. 利用EndInvoke(IAsyncResult--上一步BeginInvoke返回的对象)方法就可以结束异步操作,获取委托的运行结果。

 

    class Program
    {
        //除了最后两个参数,前面的都是你可定义的
        delegate string MyDelegate(string name,int age);
        static void Main(string[] args)
        {
            //建立委托
            MyDelegate myDelegate = new MyDelegate(GetString);
            //异步调用委托,除最后两个参数外,前面的参数都可以传进去
            IAsyncResult result = myDelegate.BeginInvoke("刘备",22, null, null);  //IAsynResult还能轮询判断,功能不弱

            Console.WriteLine("主线程继续工作!");

            //调用EndInvoke(IAsyncResult)获取运行结果,一旦调用了EndInvoke,即使结果还没来得及返回,主线程也阻塞等待了
            //注意获取返回值的方式
            string data = myDelegate.EndInvoke(result);
            Console.WriteLine(data);

            Console.ReadKey();
        }

        static string GetString(string name, int age)
        {
            Console.WriteLine("我是不是线程池线程" + Thread.CurrentThread.IsThreadPoolThread);
            Thread.Sleep(2000);
            return string.Format("我是{0},今年{1}岁!",name,age);
        }
    }

 

  输出如下:

  C# 异步线程调用方法 实现

  这种方法有一个缺点,就是不知道异步操作什么时候执行完,什么时候开始调用EndInvoke,因为一旦EndInvoke主线程就会处于阻塞等待状态。

 

5:IAsyncResult轮询

为了克服上面提到的缺点,此时可以好好利用IAsyncResult提高主线程的工作性能,IAsyncResult有如下成员。

 

public interface IAsyncResult
{
  object AsyncState {get;}       //获取用户定义的对象,它限定或包含关于异步操作的信息。
  WailHandle AsyncWaitHandle {get;}  //获取用于等待异步操作完成的 WaitHandle。
  bool CompletedSynchronously {get;} //获取异步操作是否同步完成的指示。
  bool IsCompleted {get;}        //获取异步操作是否已完成的指示。
}

 

  示例如下:

 

    class Program
    {
        delegate string MyDelegate(string name,int age);
        static void Main(string[] args)
        {
            MyDelegate myDelegate = new MyDelegate(GetString);
            IAsyncResult result = myDelegate.BeginInvoke("刘备",22, null, null);

            Console.WriteLine("主线程继续工作!");

            //比上个例子,只是利用多了一个IsCompleted属性,来判断异步线程是否完成
            while (!result.IsCompleted)
            {
                Thread.Sleep(500);          
                Console.WriteLine("异步线程还没完成,主线程干其他事!");
            }

            string data = myDelegate.EndInvoke(result);
            Console.WriteLine(data);

            Console.ReadKey();
        }

        static string GetString(string name, int age)
        {
            Thread.Sleep(2000);
            return string.Format("我是{0},今年{1}岁!",name,age);
        }
    }

 

  输出如下:

  C# 异步线程调用方法 实现

  以上例子,除了IsCompleted属性外,还可以使用AsyncWaitHandle如下3个方法实现同样轮询判断效果:

  • WaitOne:判断单个异步线程是否完成;
  • WaitAny:判断是否异步线程是否有指定数量个已完成;
  • WaitAll:判断是否所有的异步线程已完成;

  WaitOne:

  //比上个例子,判断条件由IsCompleted属性换成了AsyncWaitHandle,仅此而已
  while (!result.AsyncWaitHandle.WaitOne(200))
  {
      Console.WriteLine("异步线程没完,主线程继续干活!");
  }

  WaitAny:

  //是否完成了指定数量
  WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle };
  while (WaitHandle.WaitAny(waitHandleList, 200) > 0)
  {
      Console.WriteLine("异步线程完成数未大于0,主线程继续甘其他事!");
  }

  WaitAll:

  WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle };
  //是否全部异步线程完成
  while (!WaitHandle.WaitAll(waitHandleList, 200))
  {
      Console.WriteLine("异步线程未全部完成,主线程继续干其他事!");
  }

 

6:IAsyncResult回调函数

  使用轮询方式来检测异步方法的状态非常麻烦,而且影响了主线程,效率不高。能不能异步线程完成了就直接调用实现定义好的处理函数呢?

  有,还是强大的IAsyncResult对象。

 

    class Program
    {
        delegate string MyDelegate(string name, int age);

        static void Main(string[] args)
        {
            //建立委托
            MyDelegate myDelegate = new MyDelegate(GetString);
            //倒数第二个参数,委托中绑定了完成后的回调方法
            IAsyncResult result1 = myDelegate.BeginInvoke("刘备",23, new AsyncCallback(Completed), null);
            //主线程可以继续工作而不需要等待
            Console.WriteLine("我是主线程,我干我的活,不再理你!");
            Thread.Sleep(5000);
            //Console.ReadKey();
        }

        static string GetString(string name, int age)
        {
            Thread.CurrentThread.Name = "异步线程";
            //注意,如果不设置为前台线程,则主线程完成后就直接卸载程序了
            //Thread.CurrentThread.IsBackground = false;
            Thread.Sleep(2000);
            return string.Format("我是{0},今年{1}岁!", name, age);
        }

        //供异步线程完成回调的方法
        static void Completed(IAsyncResult result)
        {
            //获取委托对象,调用EndInvoke方法获取运行结果
            AsyncResult _result = (AsyncResult)result;
            MyDelegate myDelegaate = (MyDelegate)_result.AsyncDelegate;
            //获得参数
            string data = myDelegaate.EndInvoke(_result);
            Console.WriteLine(data);
            //异步线程执行完毕
            Console.WriteLine("异步线程完成咯!");
            Console.WriteLine("回调函数也是由" + Thread.CurrentThread.Name + "调用的!");
        }
    }

 

  输出如下:

  C# 异步线程调用方法 实现

  注意:

  1. 回调函数依然是在辅助线程中执行的,这样就不会影响主线程的运行。
  2. 线程池的线程默认是后台线程。但是如果主线程比辅助线程优先完成,那么程序已经卸载,回调函数未必会执行。如果不希望丢失回调函数中的操作,要么把异步线程设为前台线程,要么确保主线程将比辅助线程迟完成。5: