根据其属性执行多线程/单线程操作

时间:2013-11-04 22:36:12

标签: c# multithreading

鉴于可以并行运行的操作列表,我想考虑一个或另一个操作不希望与其他操作并行执行(由属性确定)的行动)。

考虑这种简化:

private static Random random;

static void Main (string[] args)
{
  random = new Random();
  new Thread (() => DoSomething (false)).Start();
  new Thread (() => DoSomething (false)).Start();
  new Thread (() => DoSomething (true)).Start();
  new Thread (() => DoSomething (false)).Start();
  new Thread (() => DoSomething (false)).Start();
  Console.Read();

}

private static void DoSomething(bool singlethread)
{
  Console.WriteLine ("Entering " + Thread.CurrentThread.ManagedThreadId);
  if (singlethread)
    Console.WriteLine ("Im the only one!!!");
  Thread.Sleep (random.Next (1000, 5000));
  Console.WriteLine ("Exiting " + Thread.CurrentThread.ManagedThreadId);
}

如何同步动作,以便动作3等待动作1& 2退出然后阻止动作4和5?

更新

这是我在Rob的帮助下得出的结论:

public class Lock : IDisposable
{
  private static readonly object _object = new object();
  private static readonly AutoResetEvent _event = new AutoResetEvent (false);
  private static int _count;

  public static IDisposable Get (bool exclusive)
  {
    return new Lock (exclusive);
  }

  private readonly bool _wasTaken;

  private Lock (bool exclusive)
  {
    if (exclusive)
    {
      Monitor.Enter (_object, ref _wasTaken);
      _count++;

      while (_count > 1)
        _event.WaitOne();
    }
    else
    {
      lock (_object)
        Interlocked.Increment (ref _count);
    }
  }

  public void Dispose ()
  {
    Interlocked.Decrement (ref _count);
    if (_wasTaken)
      Monitor.Exit (_object);
    _event.Set();
  }
}

使用方法如下:

using (Lock.Get(exclusive: false/true)
{
  DoSomething();
}

4 个答案:

答案 0 :(得分:4)

您可以在名为ConcurrentExclusiveSchedulerPair的特殊调度程序上启动Thread,而不是Task。它允许您将任务声明为并发或独占。

它的作用与ReaderWriterLockSlim的作用相同,并且让并发就绪的工作项采用读锁定,同时使独占项采用写锁定。

如果我理解你的情况,这应该满足你的需要,并且不再需要在这个帖子中发布的所有手动同步代码。

答案 1 :(得分:2)

这是一个非常常见的心理障碍,解决方案是微不足道的。您的要求是按顺序执行方法A,B和C.因此,让我们假设您启动一个执行A的线程,然后将所有锁定放在适当的位置以确保另一个线程在A完成之前不执行B.

您所忽略的是A完成后执行A的线程将要执行的操作。从你的臀部开始,你可能会说你允许它终止,该线程的工作已经完成。你启动它来执行A,你只需要它。您的锁定设计将确保另一个线程在正确的时间执行B.

但是,等等,你已经有一个非常好的候选人来执行B.执行A的线程。你没有用它来做任何事情。所以只需将该工作交给同一个线程。巨大的优势,你根本不需要任何锁定来确保这个线程在正确的时间执行B:

void DoWork(object state) {
    A();
    B();
    C();
    ItIsReallyDone.Set();
}

轻松,轻松。

答案 2 :(得分:2)

根据usr的建议,这是另一种无需手动同步即可实现的实现。

class Program
{
    private static readonly Random Random = new Random();
    private static readonly ConcurrentExclusiveSchedulerPair TaskSchedulerPair = new ConcurrentExclusiveSchedulerPair();

    static void Main()
    {
        DoSomething(false);
        DoSomething(false);
        DoSomething(true);
        DoSomething(false);
        DoSomething(false);
        Console.Read();
    }

    private static void DoSomething(bool singleThread)
    {
        var scheduler = singleThread ? TaskSchedulerPair.ExclusiveScheduler : TaskSchedulerPair.ConcurrentScheduler;

        Task.Factory.StartNew(() =>
        {
            if (singleThread)
                Console.WriteLine("Starting exclusive task.");
            DoSomething();
        }, new CancellationToken(), TaskCreationOptions.LongRunning, scheduler)
            .ContinueWith(_ =>
            {
                if (singleThread)
                    Console.WriteLine("Finished exclusive task.");
            });
    }

    private static void DoSomething()
    {
        Console.WriteLine("Starting task on thread {0}.", Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(Random.Next(1000, 5000));
        Console.WriteLine("Finished task on thread {0}.", Thread.CurrentThread.ManagedThreadId);
    }
}

答案 3 :(得分:1)

有趣的问题。您可以使用引用计数。

我已经在控制台上测试了几次,看起来它做得对了。

class Program
{
    private static Random random;
    private static int refCount;
    private static object syncObject = new object();
    private static AutoResetEvent resetEvent = new AutoResetEvent(true);

    static void Main(string[] args)
    {
        random = new Random();
        new Thread(() => DoSomething(false)).Start();
        new Thread(() => DoSomething(false)).Start();
        new Thread(() => DoSomething(true)).Start();
        new Thread(() => DoSomething(false)).Start();
        new Thread(() => DoSomething(false)).Start();
        Console.Read();
    }

    private static void DoSomething(bool singleThread)
    {
        if (singleThread)
        {
            lock (syncObject)
            {
                ++refCount;
                // Inside this lock, no new thread can get to the parameterless DoSomething() method.  
                // However, existing threads can continue to do their work, and notify this thread that they have finished by signalling using the resetEvent.
                while (refCount > 1)
                    resetEvent.WaitOne();
                Console.WriteLine("Starting exclusive task.");
                DoSomething();
                Console.WriteLine("Finished exclusive task.");
            }
        }
        else
        {
            lock (syncObject)
                ++refCount;

            DoSomething();
        }

        --refCount;
        resetEvent.Set();
    }

    private static void DoSomething()
    {
        Console.WriteLine("Starting task on thread {0}", Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(1000);
        Console.WriteLine("Finished task on thread {0}", Thread.CurrentThread.ManagedThreadId);
    }
}