c# 进程之间的线程同步

  Mutex类、Event类、SemaphoreSlim类和ReaderWriterLockSlim类等提供了多个进程之间的线程同步。

 1、WaitHandle 基类

  WaitHandle抽象类,用于等待一个信号的设置。可以根据其派生类的不同,等待不同的信号。异步委托的BeginInvoke()方法返回一个实现了IAsycResult接口的对象。使用IAsycResult接口可以用AsycWaitHandle属性访问WaitHandle基类。在调用WaitOne()方法时,线程会等待接收一个和等待句柄相关的信号:

static void Main(string[] args)
{
  Func<int> func = new Func<int>(
    () =>
    {
      Thread.Sleep(1500);
      return 1;
    });
  IAsyncResult ar = func.BeginInvoke(null, null);
  int count = 0;
  while (true)
  {
    Interlocked.Increment(ref count);
    Console.WriteLine("第{0}周期循环等待结果。", count);
    if (ar.AsyncWaitHandle.WaitOne(100, false))
    {
      Console.WriteLine("获得返回结果。");
      break;
    }
  }
  int result = func.EndInvoke(ar);
  Console.WriteLine("结果为:{0}", result);
}

  使用WaitHandle基类可以等待一个信号的出现(WaitHandle()方法)、等待多个对象都必须发出信号(WaitAll()方法)、等待多个对象中任一一个发出信号(WaitAny()方法)。其中WaitAll()方法和WaitAny()方法时WaitHandle类的静态方法,接收一个WaitHandle参数数组。

  WaitHandle基类的SafeWaitHandle属性,其中可以将一个本机句柄赋予一个系统资源,等待该句柄,如I/O操作,或者自定义的句柄。

2、Mutex 类

  Mutex类继承自WaitHandle类,提供跨多个进程同步访问的一个类。类似于Monitor类,只能有一个线程拥有锁定。在Mutex类的构造函数各参数含义:

  • initiallyOwned: 如果为 true,则给予调用线程已命名的系统互斥体的初始所属权(如果已命名的系统互斥体是通过此调用创建的);否则为 false。
  • name:系统互斥体的名称。 如果值为 null,则 System.Threading.Mutex 是未命名的。
  • createdNew: 在此方法返回时,如果创建了局部互斥体(即,如果 name 为 null 或空字符串)或指定的命名系统互斥体,则包含布尔值 true;如果指定的命名系统互斥体已存在,则为false。 该参数未经初始化即被传递。
  • mutexSecurity: 一个 System.Security.AccessControl.MutexSecurity 对象,表示应用于已命名的系统互斥体的访问控制安全性。

  互斥也可以在另一个进程中定义,操作系统能够识别有名称的互斥,它由进程之间共享。如果没有指定互斥的名称,则不在不同的进程之间共享。该方法可以检测程序是否已运行,可以禁止程序启动两次。

static void Main(string[] args)
{
  // ThreadingTimer();
  // TimersTimer();
  bool isCreateNew = false;
  Mutex mutex = new Mutex(false, "MyApp", out isCreateNew);//查询是否已有互斥“MyApp”存在
  if(isCreateNew==false)
  {
    //已存在互斥
  }
}

  要打开已有互斥,可以使用Mutex.OpenExisting()方法,不需要构造函数创建互斥时需要的相同.Net权限。可以使用WaitOne()方法获得互斥的锁定,成为该互斥的拥有着。调用ReleaseMutex()方法释放互斥:

if(mutex.WaitOne())//设置互斥锁定
{
  try
  {
    //执行代码
  }
  finally {
    mutex.ReleaseMutex();//释放互斥
  }
}
else
{
  //出现问题
}

3、Semaphore 类

  信号量是一种计数的互斥锁定,可以同时由多个线程使用。信号量可定义允许同时访问受旗语锁定保护的资源的线程个数。Semaphore和SemaphoreSlim两个类具有信号量功能。Semaphore类可以指定名称,让其在系统资源范围内查找到,允许在不同的进程之间同步。Semaphore类是对较短等待时间进行优化了的轻型版本。

static void Main()
{
  int taskCount = 6;
  int semaphoreCount = 3;
  Semaphore semaphore = new Semaphore(semaphoreCount, semaphoreCount, "Test");//创建计数为3的信号量
  /* 第一个参数为初始释放的锁定数,第二个参数为可锁定的总数。如果第一个参数小于第二个参数,其差值就是已分配线程的计量数。
   * 第三个参数为信号指定的名称,能让它在不同的进程之间共享。
   */
  var tasks = new Task[taskCount];

  for (int i = 0; i < taskCount; i++)
  {
    tasks[i] = Task.Run(() => TaskMain(semaphore));//创建6个任务
  }

  Task.WaitAll(tasks);

  Console.WriteLine("All tasks finished");
}

//锁定信号的任务
static void TaskMain(Semaphore semaphore)
{
  bool isCompleted = false;
  while (!isCompleted)//循环等待被释放的信号量
  {
    if (semaphore.WaitOne(600))//最长等待600ms
    {
      try
      {
        Console.WriteLine("Task {0} locks the semaphore", Task.CurrentId);
        Thread.Sleep(2000);//2s后释放信号
      }
      finally
      {
        Console.WriteLine("Task {0} releases the semaphore", Task.CurrentId);
        semaphore.Release();//释放信号量
        isCompleted = true;
      }
    }
    else
    {
      //超过规定的等待时间,写入一条超时等待的信息
      Console.WriteLine("Timeout for task {0}; wait again", Task.CurrentId);
    }
  }
}

  以上方法中,信号量计数为3,因此最多只有三个任务可获得锁定,第4个及以后的任务必须等待。在解除锁定时,任何情况下一定要解除资源的锁定。

4、Events 类

  事件也是一个系统范围内资源同步的方法。主要由以下几个类提供:ManualResetEvent、AutoResetEvent、ManualResetEventSlim、和CountdownEvent类。

  ManualResetEventSlim类中,调用Set()方法可以发出信号;调用Reset()方法可以使重置为无信号状态。如果多个线程在等待向一个事件发出信号,并调用Set()方法,就释放所有等待线程。如果一个线程刚刚调用了WiatOne()方法,但事件已发出信号,等待的线程就可以继续等待。

  AutoResetEvent类中,同样可以通过Set()方法发出信号、Reset()方法重置信号,但是该类是自动重置信号。如果一个线程在等待自动重置的事件发信号,当第一个线程的等待状态结束时,该事件会自动变为不发信号的状态。即:如果多个线程在等待向事件发信号,只有一个线程结束其等待状态,它不是等待事件最长的线程,而是优先级最高的线程。

//计算数据的类,使用ManualResetEventSlim类的示例
public class Calculator
{
  private ManualResetEventSlim mEvent;
  public int Result { get; private set; }
  public Calculator(ManualResetEventSlim ev)
  {
    this.mEvent = ev;
  }
  public void Calculation(int x, int y)
  {
    Console.WriteLine("Task {0} starts calculation", Task.CurrentId);
    Thread.Sleep(new Random().Next(3000));//随机等待事件
    Result = x + y;//计算结果

    Console.WriteLine("Task {0} is ready", Task.CurrentId);
    mEvent.Set();//发出完成信号
  }
}
//外部调用的示例:
static void Main()
{
  const int taskCount = 10;

  ManualResetEventSlim[] mEvents = new ManualResetEventSlim[taskCount];

  WaitHandle[] waitHandles = new WaitHandle[taskCount];
  var calcs = new Calculator[taskCount];

  for (int i = 0; i < taskCount; i++)
  {
    int i1 = i;//目的是使后面要执行的Task不必等待执行完后才释放i,让for继续
    mEvents[i] = new ManualResetEventSlim(false);//对应任务的事件对象发出信号
    waitHandles[i] = mEvents[i].WaitHandle;//ManualResetEvent类派生自WaitHandle类,但ManualResetEventSlim并不是,因此需要保存其WaitHandle对象
    calcs[i] = new Calculator(mEvents[i]);

    Task.Run(() => calcs[i1].Calculation(i1 + 1, i1 + 3));
  }

  for (int i = 0; i < taskCount; i++)
  {
    int index = WaitHandle.WaitAny(waitHandles);//等待任何一个发出信号,并返回发出信号的索引
    if (index == WaitHandle.WaitTimeout)
    {
      Console.WriteLine("Timeout!!");
    }
    else
    {
      mEvents[index].Reset();//重新设置为无信号状态
      Console.WriteLine("finished task for {0}, result: {1}", index, calcs[index].Result);
    }
  }
}

  CountdownEvent类适用于:需要把一个工作任务分配到多个任务中,然后在各个任务结束后合并结果(不需要为每个任务单独创建事件对象)。每个任务不需要同步。CountdownEvent类为所有设置了事件的任务定义了一个初始数字,达到该计数后,就发出信号。

//修改计算类
public class Calculator
{
  private CountdownEvent cEvent;
  public int Result { get; private set; }
  public Calculator(CountdownEvent ev)
  {
    this.cEvent = ev;
  }
  public void Calculation(int x, int y)
  {
    Console.WriteLine("Task {0} starts calculation", Task.CurrentId);
    Thread.Sleep(new Random().Next(3000));//随机等待事件
    Result = x + y;//计算结果
    // signal the event—completed!
    Console.WriteLine("Task {0} is ready", Task.CurrentId);
    cEvent.Signal();//发出完成信号
  }
}
//修改方法调用
static void Main()
{
  const int taskCount = 10;
  CountdownEvent cEvent = new CountdownEvent(taskCount);

  WaitHandle[] waitHandles = new WaitHandle[taskCount];
  var calcs = new Calculator[taskCount];

  for (int i = 0; i < taskCount; i++)
  {
    int i1 = i;//目的是使后面要执行的Task不必等待执行后才释放i,让for可以继续
    calcs[i] = new Calculator(cEvent);//为每个任务都赋该事件通知类
    Task.Run(() => calcs[i1].Calculation(i1 + 1, i1 + 3));

  }

  cEvent.Wait();//等待一个事件的信号
  Console.WriteLine("all finished");
  for (int i = 0; i < taskCount; i++)
  {
    Console.WriteLine("task for {0}, result: {1}", i, calcs[i].Result);
  }
}

 5、Barrier 类

  Barrier类适用于:工作有多个任务分支,并且在所有任务执行完后需要合并的工作情况。与CountdownEvent不同于,该类用于需要同步的参与者。在激活一个任务后,可以动态的添加其他参与者。在主参与者继续之前,可以等待所有其他参与者完成工作。

static void Main()
{
  const int numberTasks = 2;
  const int partitionSize = 1000000;
  var data = new List<string>(FillData(partitionSize * numberTasks));
  var barrier = new Barrier(numberTasks + 1);//定义三个参与者:一个主参与者(分配任务者),两个子参与者(被分配任务者)
  var tasks = new Task<int[]>[numberTasks];//两个子参与者
  for (int i = 0; i < numberTasks; i++)
  {
    int jobNumber = i;
    tasks[i] = Task.Run(() => CalculationInTask(jobNumber, partitionSize, barrier, data));//启动计算任务:可以分开写,以执行多个不同的任务。
  }
  barrier.SignalAndWait();// 主参与者以完成,等待子参与者全部完成。
  //合并两个结果(LINQ)
  IEnumerable<int> resultCollection = tasks[0].Result.Zip(tasks[1].Result, (c1, c2) => { return c1 + c2; }).ToList();//立即求和

  char ch = 'a';
  int sum = 0;
  foreach (var x in resultCollection)
  {
    Console.WriteLine("{0}, count: {1}", ch++, x);//输出结果
    sum += x;
  }
  Console.WriteLine("main finished {0}", sum);//统计过的字符串数量
  Console.WriteLine("remaining {0}, phase {1}", barrier.ParticipantsRemaining, barrier.CurrentPhaseNumber);//当前参与者信息
}

static int[] CalculationInTask(int jobNumber, int partitionSize, Barrier barrier, IList<string> coll)
{
  var data = new List<string>(coll);
  int start = jobNumber * partitionSize;//计算其实下标
  int end = start + partitionSize;//计算结束的位置
  Console.WriteLine("Task {0}: partition from {1} to {2}", Task.CurrentId, start, end);
  int[] charCount = new int[26];
  for (int j = start; j < end; j++)
  {
    char c = data[j][0];//获取当前字符串的第一个字符
    charCount[c - 97]++;//对应字符的数量+1;
  }
  Console.WriteLine("Calculation completed from task {0}. {1} times a, {2} times z", Task.CurrentId, charCount[0], charCount[25]);//告知计算完成
  barrier.RemoveParticipant();//告知,减少一个参数者
  Console.WriteLine("Task {0} removed from barrier, remaining participants {1}", Task.CurrentId, barrier.ParticipantsRemaining);
  return charCount;//返回统计的结果
}

//用于填充一个字符串链表
public static IEnumerable<string> FillData(int size)
{
  var data = new List<string>(size);
  var r = new Random();
  for (int i = 0; i < size; i++)
  {
    data.Add(GetString(r));//获得一个随机的字符串
  }
  return data;
}
private static string GetString(Random r)
{
  var sb = new StringBuilder(6);
  for (int i = 0; i < 6; i++)
  {
    sb.Append((char)(r.Next(26) + 97));//创建一个6个字符的随机字符串
  }
  return sb.ToString();
}

6、ReaderWriterLockSlim 类

  该类是使锁定机制允许锁定多个读取器(而不是一个写入器)访问某个资源:如果没有写入器锁定资源,那么允许多个读取器访问资源,但只能有一个写入器锁定该资源。

  由它的属性可以读取是否处于堵塞或不堵塞的锁定,如EnterReadLock()和TryEnterReadLock()方法。也可以获得其是否处于写入锁定或非锁定状态,如EnterWriteLock()和TryEnterWriteLock()方法。如果任务需要先读取资源,之后写入资源,可以使用EnterUpgradeableReadLock()或TryEnterUpgradeableReadLock()方法获取可升级的读取锁定。该锁定可以获取写入锁定,而不需要释放读取锁定。

class Program
{
  private static List<int> items = new List<int>() { 0, 1, 2, 3, 4, 5 };
  private static ReaderWriterLockSlim rwl = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

  static void ReaderMethod(object reader)
  {
    try
    {
      rwl.EnterReadLock();
      for (int i = 0; i < items.Count; i++)
      {
        Console.WriteLine("reader {0}, loop: {1}, item: {2}", reader, i, items[i]);
        Thread.Sleep(40);
      }
    }
    finally
    {
      rwl.ExitReadLock();
    }
  }

  static void WriterMethod(object writer)
  {
    try
    {
      while (!rwl.TryEnterWriteLock(50))
      {
        Console.WriteLine("Writer {0} waiting for the write lock", writer);
        Console.WriteLine("current reader count: {0}", rwl.CurrentReadCount);
      }
      Console.WriteLine("Writer {0} acquired the lock", writer);
      for (int i = 0; i < items.Count; i++)
      {
        items[i]++;
        Thread.Sleep(50);
      }
      Console.WriteLine("Writer {0} finished", writer);
    }
    finally
    {
      rwl.ExitWriteLock();
    }
  }

  static void Main()
  {
    var taskFactory = new TaskFactory(TaskCreationOptions.LongRunning, TaskContinuationOptions.None);
    var tasks = new Task[6];
    tasks[0] = taskFactory.StartNew(WriterMethod, 1);
    tasks[1] = taskFactory.StartNew(ReaderMethod, 1);
    tasks[2] = taskFactory.StartNew(ReaderMethod, 2);
    tasks[3] = taskFactory.StartNew(WriterMethod, 2);
    tasks[4] = taskFactory.StartNew(ReaderMethod, 3);
    tasks[5] = taskFactory.StartNew(ReaderMethod, 4);

    for (int i = 0; i < 6; i++)
    {
      tasks[i].Wait();
    }
  }
}

以上就是c# 进程之间的线程同步的详细内容,更多关于c# 线程同步的资料请关注我们其它相关文章!

(0)

相关推荐

  • 如何使用C# 捕获进程输出

    Intro 很多时候我们可能会需要执行一段命令获取一个输出,遇到的比较典型的就是之前我们需要用 FFMpeg 实现视频的编码压缩水印等一系列操作,当时使用的是 FFMpegCore 这个类库,这个类库的实现原理是启动另外一个进程,启动 ffmpeg 并传递相应的处理参数,并根据进程输出获取处理进度 为了方便使用,实现了两个帮助类来方便的获取进程的输出,分别是 ProcessExecutor 和 CommandRunner,前者更为灵活,可以通过事件添加自己的额外事件订阅处理,后者为简化版,主要是

  • 一个进程间通讯同步的C#框架引荐

     0.背景简介 微软在 .NET 框架中提供了多种实用的线程同步手段,其中包括 monitor 类及 reader-writer锁.但跨进程的同步方法还是非常欠缺.另外,目前也没有方便的线程间及进程间传递消息的方法.例如C/S和SOA,又或者生产者/消费者模式中就常常需要传递消息.为此我编写了一个独立完整的框架,实现了跨线程和跨进程的同步和通讯.这框架内包含了信号量,信箱,内存映射文件,阻塞通道,及简单消息流控制器等组件.这篇文章里提到的类同属于一个开源的库项目(BSD许可),你可以从这里下载到

  • C#结束Excel进程的步骤教学

    程序中,系统自带的回收功能无法马上结束Excel进程,需要自己编写程序. 1.项目中,引用添加office COM组件 2.引用接口 3.在程序中 添加代码 [DllImport("User32.dll", CharSet = CharSet.Auto)] public static extern int GetWindowThreadProcessId(IntPtr hwnd, out int ID); 4.停止Excel应用,这一步不会马上结束Excel进程,但是需要写 5.立即结

  • 详解C#多线程编程之进程与线程

    一. 进程 简单来说,进程是对资源的抽象,是资源的容器,在传统操作系统中,进程是资源分配的基本单位,而且是执行的基本单位,进程支持并发执行,因为每个进程有独立的数据,独立的堆栈空间.一个程序想要并发执行,开多个进程即可. Q1:在单核下,进程之间如何同时执行? 首先要区分两个概念--并发和并行 并发:并发是指在一段微小的时间段中,有多个程序代码段被CPU执行,宏观上表现出来就是多个程序能"同时"执行. 并行:并行是指在一个时间点,有多个程序段代码被CPU执行,它才是真正的同时执行. 所

  • 如何利用c#实现通用守护进程

    1. 下载 源码下载:http://files.cnblogs.com/tianzhiliang/CocoWatcher.rar 安装包下载:http://files.cnblogs.com/tianzhiliang/CocoWatcher_Setup.rar 本地下载:http://xiazai.jb51.net/201910/yuanma/CocoWatcher(jb51net).rar 2. 安装注意事项 在配置档中配置你要守护的应用程序,应用程序之间用逗号隔开: <?xml versio

  • 利用C#编写Linux守护进程实例代码

    前言 Linux守护进程是Linux的后台服务进程,相当于Windows服务,对于为Linux开发服务程序的朋友来说,Linux守护进程相关技术是必不可少的,因为这个技术不仅仅是为了开发守护进程,还可以拓展到多进程,父子进程文件描述符共享,父子进程通讯.控制等方面,是实现Linux大型服务的基础技术之一. 如果要在Red Hat Enterprise Linux上将.NET Core进程作为后台进程运行,则可以创建自定义systemd单元.今天我将为.NET Core编写两个自定义系统单元的例子

  • C#获取所有进程的方法

    在使用C#进行相关编程的时候,有时候我们需要获取系统相关的进程信息.那么在C#中如何获取系统的所有进程那?下面请跟小编一起来操作. 1.首先新建一个控制台程序,这里主要是为了方便演示,控制台程序相对比较简单,如下图所示: 2.然后导入进程相关的操作类,主要是diagnostics,如下图所示 3.然后我们调用Process类的GetProcesses方法,获取系统所以进程,注意是一个数组,图下图所示: 4.我们来看一下Process的相关解释说明,把鼠标放上去,看到如下图所示的内容 5.接下来我

  • 详解C#获取特定进程CPU和内存使用率

    首先是获取特定进程对象,可以使用Process.GetProcesses()方法来获取系统中运行的所有进程,或者使用Process.GetCurrentProcess()方法来获取当前程序所对应的进程对象.当有了进程对象后,可以通过进程对象名称来创建PerformanceCounter类型对象,通过设定PerformanceCounter构造函数的参数实现获取特定进程的CPU和内存使用情况. 具体实例代码如下: 首先是获取本机中所有进程对象,分别输出某一时刻各个进程的内存使用情况: using

  • C#向无窗口的进程发送消息

    注:本文适用.net2.0+的winform程序 一个winform程序,我希望它不能多开,那么在用户启动第二个实例的时候,作为第二个实例来说,大概可以有这么几种做法: 1.弹个窗告知用户[程序已运行]之类,用户点击弹窗后,退出自身 2.什么都不做,默默退出自身 3.让已运行的第一个实例把它的窗体显示出来,完了退出自身 显然第3种做法更地道,实现该效果的核心问题其实是:如何显示指定进程的窗口? 首先想到的是调用ShowWindow.SetForegroundWindow等API,配合使用可以将被

  • C# 获取进程退出代码的实现示例

    我需要写一个程序,让这个程序知道另一个程序是否正常退出,于是就需要获取这个进程的退出代码 在程序如果需要手动退出,可以设置当前的退出代码 static void Main(string[] args) { Environment.Exit(-100); } 这时的程序运行就退出,同时退出的代码就是 -100 这和 C 语言的在 main 函数返回值一样 在 C# 如果想要实现 C 语言的 main 函数的返回值,是通过调用 Environment.Exit 方法 那么其他程序如何拿到这个程序的退

  • c# 进程内部的同步

    在线程里,如果需要共享数据,那么一定需要使用同步技术,确保一次只有一个线程访问和改变共享数据的状态.在.net中,lock语句.Interlocked类和Monitor类可用于进程内部的同步. 1.lock语句与线程安全 lock语句是设置锁定和解除锁定的一种简单方式.在使用lock语句之前,先进入另一个争用条件.例如: public class SharedState { public int State { get; set; } } public class Job { SharedSta

随机推荐