C#沉淀-异步编程 四

C#沉淀-异步编程 四_第1张图片

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();
        }
    }
}

你可能感兴趣的:(C#沉淀-异步编程 四)