C#线程同步方式

一、进程内部的线程同步

1、使用lock,用法如下:

        private static readonly object SeqLock = new object();

        private void Print()
        {
            lock (SeqLock)
            {
                Console.WriteLine("test");
            }
        }

特性:只能传递对象,无法设置等待超时

2、使用:InterLocked(原子操作)

其在System.Threading命名空间下,Interlocked实际是类控制计数器,从而实现进程的同步,其很容易实现生产者消费者模型

 //缓冲区,只能容纳一个字符
      private static char buffer;
      //标识量(缓冲区中已使用的空间,初始值为0)
      private static long numberOfUsedSpace = 0;
      static void Main(string[] args)
      {
        //线程:写入者
       Thread Writer = new Thread(delegate ()
       {
         string str = "这里面的字会一个一个读取出来,一个都不会少,,,";
         for (int i = 0; i < 24; i++)
         {
           //写入数据前检查缓冲区是否已满
           //如果已满,就进行等待,直到缓冲区中的数据被进程Reader读取为止
           while (Interlocked.Read(ref numberOfUsedSpace) == 1)
           {
             Thread.Sleep(50);
           }
           buffer = str[i];  //向缓冲区写入数据
           //写入数据后把缓冲区标记为满(由0变为1)
           Interlocked.Increment(ref numberOfUsedSpace);
         }
       });
       //线程:读出者
       Thread Reader = new Thread(delegate ()
       {
         for (int i = 0; i < 24; i++)
         {
           //读取数据前检查缓冲区是否为空
           //如果为空,就进行等待,直到进程Writer向缓冲区中写入数据为止
           while (Interlocked.Read(ref numberOfUsedSpace) == 0)
           {
             Thread.Sleep(50);
           }
           char ch = buffer;    //从缓冲区读取数据
           Console.Write(ch);
           Interlocked.Decrement(ref numberOfUsedSpace);
         }
       });
       //启动线程
       Writer.Start();
       Reader.Start();
       Console.ReadKey();

3、使用Monitor

其中Monitor.Enter()和lock相同

            Monitor.Enter(obj){
                //Synchronized part
            }finally{
                Monitor.Exit(obj);
            }

TryEnter则可设置等待时间等

            bool lockTaken=false;
            Monitor.TryEnter(obj, 500, ref lockTaken);
            if(lockTaken){
                try
                {
                    //Synchronized part
                }
                finally
                {
                    Monitor.Exit(obj);
                }
            }else{
                //don't aquire the lock, excute other parts
            }

二、进程间的同步

1. WaitHandle: 

封装等待对共享资源进行独占访问的操作系统特定的对象。 WaitHandle:是一个抽象类,我们一般不直接用,而是用它的派生类:

AutoResetEvent、EventWaitHandle、ManualResetEvent、Mutex、Semaphore

 

这个抽象类的方法如下:

WaitOne(): 等待一个信号的出现,可设置超时;

WaitAll(): 等待多个信号的出现,可设置超时;

WaitAny(): 等待任意一个信号的出现,可设置超时;

 

2、Mutex: 

与Monitor 类似,只有一个线程能够获取锁定。利用WaitOne() 获取锁定,利用ReleaseMutex() 解除锁定。构造函数使用如下:

            bool isNew = false;
            mutex = new Mutex(false, "Mutex1", out isNew);

参数1:锁创建后是否由主调线程拥有。 如果设为true,相当于调用了WaitOne(),需要释放,否则其他线程无法获取锁;

参数2:锁名称,可通过OpenExist()或TryOpenExist() 打开已有锁,因为操作系统识别有名称的互锁,所以可由不同的进程共享。若锁名称为空,就是未命名的互锁,不能在多个进程之间共享;

参数3:  是否为新创建的互锁;

下面的例子演示Mutex 在进程之间的使用:    class Program    {

        private static Mutex mutex = null;  
        static void Main(string[] args)
        {
            bool isNew = false;
            mutex = new Mutex(false, "Mutex1", out isNew);
Console.WriteLine("Main Start...."); mutex.WaitOne();
Console.WriteLine("Aquire Lock and Running...."); Thread.Sleep(10000); mutex.ReleaseMutex();
Console.WriteLine("Release Lock...."); Console.WriteLine("Main end...."); Console.ReadLine(); } }

连续2次运行这个控制台程序的exe,结果如下,首先运行的获取 Mutex1 互锁, 后面运行的会等待直到前面运行的释放 Mutex1 互锁。

 

 3.Semaphore:

 信号量的作用于互斥锁类似,但它可以定义一定数量的线程同时使用。下面是构造函数:

            bool isNew = false;
            semaphore = new Semaphore(3, 3, "semaphore1", out isNew);

参数1:创建后,最初释放的锁的数量,如参数1设为2,参数2设为3,则创建后只有2个锁可用,另1个已经锁定;

参数2:定义可用锁的数量;

参数3:  信号量的名称,与Mutex类似;

参数4:否为新创建的互锁;

以下例子创建了信号量“semaphore1”,利用Parallel.For() 同步运行Func1() ,在Func1() 中,当线程获取信号量锁,释放锁或等待超时,都会在控制台里输出,

class Program
    {
        private static Semaphore semaphore = null;
        static void Main(string[] args)
        {

            Console.WriteLine("Main Start....");
            bool isNew = false;
            semaphore = new Semaphore(3, 3, "semaphore1", out isNew);
            Parallel.For(0, 6, Func1);
            Console.WriteLine("Main end....");
            Console.ReadLine();
        }

        static void Func1(int index)
        {
            Console.WriteLine("Task {0} Start....",Task.CurrentId);
            bool isComplete = false;
            while (!isComplete)
            {
                if (semaphore.WaitOne(1000))    
                {
                    try
                    {
                        Console.WriteLine("Task {0} aquire lock....", Task.CurrentId);
                        Thread.Sleep(5000);
                    }
                    finally
                    {
                        semaphore.Release();
                        Console.WriteLine("Task {0} release lock....", Task.CurrentId);
                        isComplete = true;
                    }
                }
                else
                {
                    Console.WriteLine("Task {0} timeout....", Task.CurrentId);
                }
            }
        }

运行结果如下,线程1,2,3首先获取信号量锁,线程4,5,6在等待,直到1,2,3释放,

4. AutoResetEvent 类:

可以使用事件通知其他任务,构造函数为 public AutoResetEvent(bool initialState)。

当initialState=true,处于signaled 模式(终止状态),调用waitone() 也不会阻塞任务,等待信号,调用Reset()方法,可以设置为non-signaled 模式;

当initialState=fasle,处于non-signaled 模式(非终止状态),调用waitone() 会等待信号阻塞当前线程(可以在多个线程中调用,同时阻塞多个线程),直到调用set()发送信号释放线程(调用一次,只能释放一个线程),一般使用这种方式;

以下例子创建5个任务,分别调用waitone()阻塞线程,接着每隔2s 调用set(),

        private static AutoResetEvent autoReset = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            Console.WriteLine("Main Start....");
            for (int i = 0; i < 5; i++)
            {
                Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("{0} Start....", Task.CurrentId);
                    autoReset.WaitOne();
                    Console.WriteLine("{0} Continue....", Task.CurrentId);
                });
            }
            for (int i = 0; i < 5;i++ )
            {
                Thread.Sleep(2000);
                autoReset.Set();
            }
            Console.WriteLine("Main end....");
            Console.ReadLine();
        }

运行结果每次顺序略有不同,释放是随机的:

 5. ManualResetEvent 类:

功能基本上和AutoSetEvent类似,但又一个不同点:

使用AutoSetEvent,每次调用set(),切换到终止模式,只能释放一个waitone(),便会自动切换到非终止模式;但ManualResetEvent,调用set(),切换到终止模式,可以释放当前所有的waitone(),需要手动调用reset()才能切换到非终止模式。

以下例子说明了这个不同的:  

        private static ManualResetEvent manualReset = new ManualResetEvent(false);
        static void Main(string[] args)
        {
            Console.WriteLine("Main Start....");
            for (int i = 0; i < 5; i++)
            {
                Task.Factory.StartNew(() =>
                {
                    Console.WriteLine("{0} Start....", Task.CurrentId);
                    manualReset.WaitOne();
                    Console.WriteLine("{0} Continue....", Task.CurrentId);
                });
            }
            Thread.Sleep(2000);
            manualReset.Set();
            manualReset.WaitOne();
            Console.WriteLine("it doesn't work now, Main continue....");
            manualReset.Reset();
            manualReset.WaitOne();
            Console.WriteLine("Main end....");
            Console.ReadLine();
        }      

 6、TaskCompletionSource (承诺模式)

TaskCompletionSource 类允许你创建一个任务,并手动控制它的完成状态。它有以下几个主要方法:

  • SetResult(T result):将任务设置为已完成状态,并提供一个结果值。
  • SetException(Exception exception):将任务设置为已完成状态,并提供一个异常。
  • SetCanceled():将任务设置为已取消状态。
  • Task Task { get; }:获取与 TaskCompletionSource 关联的任务。

要使用 TaskCompletionSource,可以按照以下步骤进行:

  1. 创建一个 TaskCompletionSource 对象。
  2. 在需要的时候设置任务的状态(已完成、已取消或已出错)。
  3. 使用 Task 属性获取与 TaskCompletionSource 关联的任务。

  以下是一个使用 TaskCompletionSource 的示例:

// 创建一个 TaskCompletionSource 对象
var tcs = new TaskCompletionSource<int>();

// 在某个时刻设置任务的状态
tcs.SetResult(42);

// 获取与 TaskCompletionSource 关联的任务
var task = tcs.Task;

// 稍后可以等待任务完成
task.Wait();

// 获取任务的结果
var result = task.Result; // 42
在你的场景中,可以使用 TaskCompletionSource 来确保在 UI 线程启动后立即执行代码:

// 创建一个 TaskCompletionSource 对象
var tcs = new TaskCompletionSource<bool>();

_uiThread = new Thread(() =>
{
    application=new Application
    {
        ShutdownMode = ShutdownMode.OnExplicitShutdown
    };

    // 设置 TaskCompletionSource 任务为已完成状态
    tcs.SetResult(true);

    application.Run();
});

_uiThread.SetApartmentState(ApartmentState.STA);
_uiThread.IsBackground = true;
_uiThread.Start();

// 等待 TaskCompletionSource 任务完成
await tcs.Task.ConfigureAwait(false);

await application.Dispatcher.BeginInvoke(() => {

    var win = new WinTest();
    win.Show();
});

承诺模式是一种设计模式,它允许你将异步操作的结果与操作本身分离。这使得你可以轻松地将异步操作的结果传递给其他代码,而无需担心操作的底层实现细节。

TaskCompletionSource 类提供了以下与承诺模式相符的功能:

  • **创建承诺:**你可以使用 TaskCompletionSource 的构造函数来创建承诺。
  • **设置结果:**你可以使用 SetResult 方法来设置承诺的结果。
  • **获取承诺:**你可以使用 Task 属性来获取与 TaskCompletionSource 关联的承诺。

此外,TaskCompletionSource 类还提供了以下附加功能:

  • **设置异常:**你可以使用 SetException 方法来设置承诺的异常。
  • **设置取消:**你可以使用 SetCanceled 方法来设置承诺的取消。

这些附加功能使 TaskCompletionSource 类成为在 .NET 中实现承诺模式的强大工具。

 

posted @ 2020-07-27 17:05  卖雨伞的小男孩  阅读(297)  评论(0编辑  收藏  举报