C#学习笔记14

1.在多个线程的同步数据中,避免使用this、typeof(type)、string进行同步锁,使用这3个容易形成死锁。编程

2.使用Interlocked类:咱们通常使用的互斥锁定模式(同步数据)为Lock关键字(即Monitor类),这个同步属于代价很是高的一种操做。除了使用Monitor以外,还有一个备选方案,它一般直接由处理器支持,并且面向特定的同步模式。Interlocked类中包含一些经常使用方法,如CompareExchange、Decrement、Increment、Exchange。这些都是针对单个的值(对象)进行同步数据处理。安全

3.多个线程时的事件通知:能够查看Utility.EventInThread()代码清单。服务器

4.同步设计的最佳实践:多线程

(1)避免死锁;如两个线程都等待对方锁定的资源释放,线程A锁定sync1资源,线程B锁定sync2资源,线程A请求锁定sync2资源,线程B请求锁定sync1资源,此时便出现死锁。死锁的发生必须知足4个基本条件。互斥、占有并等待、不可抢先、循环等待条件。并发

(2)什么时候提供同步;一般针对静态数据进行同步,并有公共方法来修改数据,方法内部应处理好同步问题。框架

(3)避免没必要要的锁定。异步

5.更多同步类型:System.Threading.Mutex类在概念上与Monitor类一致,只是其是为支持进程之间的同步。如同步对文件或其余跨进程资源的访问,限制程序只能运行一个实例。如Utility.UseMutex()代码清单。Mutex类派生自WaitHandle,能够自动获取多个锁(这是Monitor类所不支持的)。async

6.WaitHandle类:多个同步类是继承于它,如Mutex、EventWaitHandle、Semaphore,WaitHandle类的关键方法为WaitOne(),它有多个重载版本,这些方法会阻塞当前线程,直到WaitHandle实例收到信号或被设置(调用Set())。分布式

7.重置事件类:重置事件与C#中委托以及事件没有任何关系,用于多线程的控制,重置事件用于强迫代码等候另外一个线程的执行,直到得到事件已发生的通知。重置事件类有ManualResetEvent、ManualResetEventSlim(.net4.0新增,针对前者进行优化)、AutoResetEvent(主要使用前面2个类型),它们提供的关键方法为Set()与Wait()。调用Wait()方法会阻塞一个线程的执行,直到一个不一样的线程调用Set(),或者设定的等待时间结束,才会继续运行。可查看Utility.UseManualResetEvent()代码清单。ide

8.并发集合类:.net4.0新增了一些类是并发集合类,这些类专门设计用来包含内建的同步代码,使它们能支持多个线程访问而没必要关心竞态条件。如BlockingCollection<T>、ConcurrentBag<T>、ConcurrentDictionary<K,V>、ConcurrentQueue<T>、ConcurrentStack<T>,利用并发集合,能够实现的一个常见的模式是生产者和消费者的线程安全的访问。

9.线程本地存储:同步的一个替代方案是隔离,而实现隔离的一个办法是使用线程本地存储,利用线程本地存储,线程就有了专属的变量实例。线程本地存储实现有2中方式,分别为ThreadLocal<T>和ThreadStaticAttribute类,其中ThreadLocal<T>类是.net4.0新增的。可查看LocalVarThread类的代码清单。

10.计时器:有三种计时器分别为System.Windows.Forms.Timer、System.Timers.Timer、System.Threading.Timer,Forms.Timer用于用户界面编程,可以安全的访问用户界面上的窗体与控件,Timers.Timer是Threading.Timer的包装器,是对其功能的抽象(System.Threading.Timer类型轻量一些)。

功能描述

System.Timers.Timer

System.Threading.Timer

System.Window.Forms.Timer

支持在计时器实例化以后添加和删除侦听器

支持用户界面线程上的回调   

从自线程池获取的线程进行回调

支持在Windows窗体设计器中拖放

适合在一个多线程服务器环境中运行

支持将任意状态从计时器初始化传递至回调

实现Idisposable

支持开关式回调和按期重复回调

可穿越应用程序域的边界访问

支持Icomponent,可容纳在一个Icontainer中

 

11.异步编程模型(Async Program Model,APM):异步编程是多线程的一种方式,APM的关键在于成对使用BeginX和EndX方法(X通常对应同步版本的方法名),并且这些方法具备完善的签名。BeginX返回一个System.IAsyncResult对象,可经过它访问异步调用的状态,以便等待完成或轮询完成。然而EndX方法获取这个返回的对象做为输入参数。这样才真正将两个方法配成一对,让咱们能够清晰地判断哪一个BeginX方法调用和哪一个EndX方法调用配对。APM的本质要求全部BeginX调用都必须有一个(并且只能有一个)EndX调用。所以,不可能发生两个EndX调用接受同一个IAsyncResult实例的状况。咱们还可使用IAsyncResult的WaitHandle判断异步方法什么时候结束,IAsyncResult的WaitHandle是在回调执行以前进行通知完成。

  EndX方法具备4个方面的用途。

  首先,调用EndX会阻塞线程继续执行,直到请求的工做成功完成(或者发生错误并引起异常)。

  其次,若是方法X要返回数据,这个数据可从EndX方法调用中访问。

  再次,若是执行请求的工做时发生异常,可在调用EndX时从新引起这个异常,确保异常会被调用代码发现——好像它是在一次同步调用上发生的那样。

  最后,若是任何资源须要在调用X后清理,EndX将负责清理这些资源。

  BeginX方法有两个额外的参数,在同步版本的方法中是没有的,一个是回调参数,是方法结束时要调用的一个System.AsyncCallback委托,另外一个是object类型的状态参数(State)。在使用回调时,能够把EndX放在其内部执行。

12.使用TPL(任务并行库)调用APM:虽然TPL大幅简化了长时间运行方法的异步调用,但一般最好是使用API提供的APM方法,而不是针对同步版本编写TPL。这是由于API开发人员知道如何编写最高效率的线程处理代码,知道同步哪些数据以及要使用什么同步类型。TPL包含FromAsync的一组重载版本,用于调用APM。

13.异步委托调用:有一个派生的APM模式,称为异步委托调用,它在全部委托数据类型上使用了特殊的、由C#编译器生成的代码。例如,给定Func<string,int>的一个委托实例,能够在这个实例上使用如下APM方法对。

  System.IAsyncResult BeginInvoke(string arg,AsyncCallback callback,object obj)

  Int EndInvode(IasyncResult result)

  结果是可使用C#编译器生成的方法来同步地调用任何委托(进而调用任何方法)。遗憾的是,异步委托调用模式使用的基础技术是一种再也不继续开发的分布式编程技术,称为远程处理。虽然微软仍然支持异步委托调用,并且在能够预见的未来,也不会放弃对它的支持,但和其余技术相比,它的性能显得比较通常。其余技术包括Thread、ThreadPool和TPL等。所以,在开发新项目时,开发人员应尽可能选用其余技术,而不要使用异步委托调用API。在TPL以前,异步委托调用模式比其余替代方案容易得多,因此假如一个API没有提供显式的异步调用模式,通常都会选用它。然而,在TPL问世以后,除非是为了与.Net3.5和早期框架版本兼容,不然异步委托调用愈来愈没有什么用了。

14.基于事件的异步模式(EAP):比APM更高级的一种编程模式是基于事件的异步模式。和APM同样,API开发人员为长时间运行的方法实现了EAP。其中Background Worker模式,它是EAP的一个特定的实现。

15.Background Worker模式:创建Background Worker模式的过程以下。

(1)为BackgroundWorker.DoWork事件注册长时间运行的方法。

(2)为了接受进展或状态通知,要为BackgroundWorker.ProgressChanged挂接一个侦听器,并将BackgroundWorker.WorkerReportsProgress设为true。

(3)为BackgroundWorker.RunWorkerCompleted事件注册一个方法。

(4)为WorkerSupportsCancellation属性赋值以支持取消一个操做。将true值赋给该属性之后,对BackgroundWorker.CancelAsync的调用就会设置DoWorkEventArgs.CancellationPending标志。

(5)在DoWork提供的方法内,检查DoWorkEventArgs.CancellationPending属性值,并在它为true时退出方法。

(6)一切都设置好以后,调用BackgroundWorker.RunWorkerAsync(),并提供要传给指定DoWork()方法的一个状态参数来开始工做。

  分解成以上小步骤之后,Background Worker模式就显得容易理解。另外,因为它本质上是一种EAP,因此提供了对进度通知的显式支持。后台的工做者(worker)线程异步执行的时候,假如发生一个未处理的异常,RunWorkerCompleted委托的RunWorkerCompletedEventArgs.Error属性就会设置成Exception实例。所以,咱们经过在RunWorkerCompleted回调内检查Error属性来提供异常处理机制。

16.Windows UI编程:使用System.Windows.Forms和System.Windows命名空间来进行用户界面开发时,也必须注意线程处理问题。Microsoft Windows系列操做系统使用的是一个单线程的、基于消息处理的用户界面。这意味着,每次只能有一个线程访问用户界面,与轮换线程的任何交互都应该经过Windows消息泵来封送。

17.Windows窗体:进行Windows窗体编程时,为了检查是否容许从一个线程中发出UI调用,须要调用一个组件的InvokeRequired属性,判断是否须要进行封送处理。若是InvokeRequired返回true,代表须要封送,并可经过一个Invoke()调用来实现。尽管Invoke()在内部不管如何都会检查InvokeRequired,但更有效率的作法是提早显示地检查这个属性。因为封送到另外一个线程多是至关慢的一个过程,因此能够经过BeginInvoke()和EndInvoke()来进行异步调用。Invoke()、BeginInvoke()、EndInvoke()和InvokeRequired构成了System.ComponentModel.ISynchronizeInvoke接口的成员。该接口已由System.Windows.Forms.Control实现,全部Windows窗体控件都是从这个Control类派生。

18.Windows Presentation Foundation(WPF):为了在WPF平台上实现相同的封送检查,须要采起稍有不一样的一种方式。WPF包含一个名为Current的静态成员属性,它的类型是DispatcherObject,由System.Windows.Application类提供。在调度器(dispatcher)对象上调用CheckAccess(),做用等同于在Windows窗体中的控件上调用InvokeRequired,而后再使用Application.Current.Dispatcher.Invoke()方法封送。

19.说明:除了TPL提供的模式,还有这么多额外的模式可供选用,这形成许多人不知道应该如何选择。通常状况下,最好是选择由API提供的模式(好比APM或EAP),最后选择TPL模式。

public class Utility
{
    private static ManualResetEventSlim firstEvent, secendEvent;
    private static object _data;
    
    public static void Initialize(object newValue)
    {
        Interlocked.CompareExchange(ref _data, newValue, null);
    }

    public static void EventInThread()
    {
        //不是线程安全,在检查委托对象与调用委托之间,存在其余线程对OnTemparatureChange进行赋值操做,可能会被设置为null。
        /*if (OnTemparatureChange != null)
        {
            //调用订阅者
            OnTemparatureChange(this, new TemparatureEventArgs());
        }*/

        //线程安全操做,建立一个委托变量副本,检查副本的null,再触发副本。这样即便OnTemparatureChange委托变量在其余线程中被null化,也不影响。
        /*TemparatureChangedHandle localChanged = OnTemparatureChange; 
        if (localChanged != null)
        {
            //调用订阅者
            localChanged(this, new TemparatureEventArgs());
        }*/
    }

    public static void UseMutex()
    {
        bool firstApplicationInstance;
        string mutexName = Assembly.GetEntryAssembly().FullName;
        using (Mutex mutex = new Mutex(false, mutexName, out firstApplicationInstance))
        {
            if (!firstApplicationInstance)
            {
                Console.WriteLine("This Application is already running.");
            }
            else
            {
                Console.WriteLine("Enter to shutdown.");
                Console.ReadLine();
            }
        }
    }

    public static void UseManualResetEvent()
    {
        using (firstEvent = new ManualResetEventSlim())
        using (secendEvent = new ManualResetEventSlim())
        {
            Console.WriteLine("App start");
            Console.WriteLine("start task");
            Task task = Task.Factory.StartNew(() =>
            {
                Console.WriteLine("DoWork start");
                Thread.Sleep(1000);
                firstEvent.Set();
                secendEvent.Wait();
                Console.WriteLine("DoWork end");
            });
            firstEvent.Wait();
            Console.WriteLine("Thread executing");
            secendEvent.Set();
            task.Wait();
            Console.WriteLine("Thread completed");
            Console.WriteLine("App shutdown");
        }
    }
}

public class LocalVarThread
{
    public static ThreadLocal<double> _count = new ThreadLocal<double>(() => 0.01134);

    public static double Count
    {
        set { _count.Value = value; }
        get { return _count.Value; }
    }

    public static void DoWork()
    {
        Task.Factory.StartNew(Decrement);
        for (int i = 0; i < short.MaxValue; i++)
        {
            Count++;
        }
        Console.WriteLine("DoWork Count = {0}", Count);
    }

    public static void Decrement()
    {
        Count = -Count;
        for (int i = 0; i < short.MaxValue; i++)
        {
            Count--;
        }
        Console.WriteLine("Decrement Count = {0}", Count);
    }
}
View Code

---------------------以上内容根据《C#本质论 第三版》进行整理

相关文章
相关标签/搜索