1

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

考虑这种简化:

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

4 回答 4

4

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

它的工作原理ReaderWriterLockSlim与让并发就绪的工作项获取读锁而让独占项获取写锁的工作方式相同。

如果我正确理解了您的情况,这应该可以满足您的需求,并且不再需要已在此线程中发布的所有手动同步代码。

于 2013-11-05T10:44:33.193 回答
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);
    }
}
于 2013-11-05T11:39:13.427 回答
2

这是一个很常见的心理障碍,解决方法很简单。您的要求是按顺序执行方法 A、B 和 C。因此,假设您启动了一个线程来执行 A,并且您将所有锁定到位以确保另一个线程在 A 完成之前不会执行 B。

您忽略的是在 A 完成后执行 A 的线程将要做什么。突然,您可能会说您将允许它终止,该线程的工作已完成。你启动它来执行 A,你需要它。您的锁定设计将确保另一个线程在正确的时间执行 B。

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

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

十分简单。

于 2013-11-05T01:23:16.023 回答
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);
    }
}
于 2013-11-05T00:10:54.860 回答