3

我有一个业务逻辑方法,必须先完成才能再次调用它。多个客户端可以一次调用它:

public void DoSomething() {}

我正在考虑通过将方法设为私有来解决它,并创建一个新的公共方法来将请求放在队列中:

public void QueueSomeWork()
{
    // put on a Queue object
    // How will DoSomething get invoked now?
}

private void DoSomething() {}

我试图以一种优雅的方式解决这个问题。我的问题是如何DoSomething()知道运行。我考虑过创建一个计时器来检查队列,但是对于每年可能发生两次的事情,它将全天候运行 24/7。

另一个想法是在DoSomething()完成后触发一个其他东西会订阅的事件,从队列中挑选一些工作,然后调用DoSomething(). 有没有更好的办法?

4

5 回答 5

10

为什么不使用防盗锁?

例如:

   private static Object lockGuard = new Object();
   public void DoSomething()
   {
     lock (lockGuard) 
      {
          //logic gere
      }
    }

锁定一个资源将阻止多个线程同时访问。

更多关于锁: http: //msdn.microsoft.com/en-us/library/c5kehkcz (v=vs.110).aspx

于 2013-06-07T20:33:46.550 回答
3

如果数字不是那么高(这取决于DoSomething内部如何消耗资源);我会这样做:

public static async void QueueSomeWork()
{
    await Task.Run(() => { DoSomething(); });
}

static readonly object lockObject = new object();
static void DoSomething()
{
    lock (lockObject)
    {
        // implementation
    }
}

如果数字更高,您应该限制允许的排队任务的数量:

static long numberOfQueuedTasks = 0;
const long MAX_TASKS = 10000; // it depends how DoSomething internals consume resource
public static async void QueueSomeWork()
{
    if (numberOfQueuedTasks > MAX_TASKS)
    {
        var wait = new SpinWait();

        while (numberOfQueuedTasks > MAX_TASKS) wait.SpinOnce();
    }

    await Task.Run(() => { Interlocked.Increment(ref numberOfQueuedTasks); DoSomething(); });
}

static readonly object lockObject = new object();
static void DoSomething()
{
    try
    {
        lock (lockObject)
        {
            // implementation
        }
    }
    finally
    {
        Interlocked.Decrement(ref numberOfQueuedTasks);
    }
}
于 2013-06-07T20:53:04.510 回答
2

简单的方法是用 装饰方法MethodImplOptions.Synchronized,其功能类似于synchronizedJava 中的关键字:

[MethodImpl(MethodImplOptions.Synchronized)]
private void DoSomething()
{
    // ...
}

主要的缺点是这将锁定当前实例,如果您已经在其他地方使用锁定,这可能会导致死锁。

于 2013-06-07T20:52:54.577 回答
0

这是一个想法。您可能希望doSomethingCount在使用它时锁定它,但至于排队 DoSomething 并继续这样做可能会起作用,因为它在单独的线程上运行。既然你对队列没意见,我假设你想开火就忘了,实际上不需要阻止来电者。

    // This will increment the count and kick off the process of going through
    // the calls if it isn't already running. When it is done, it nulls out the task again
    // to be recreated when something is queued again.
    public static void QueueSomething()
    {
        doSomethingCount++;
        if (doSomethingTask == null)
        {
            doSomethingTask =
                Task.Run((Action)(() =>
                {
                    while (doSomethingCount > 0)
                    {
                        DoSomething();
                        doSomethingCount--;
                    }
                }))
                .ContinueWith(t => doSomethingTask = null);
        }
    }

    // I just put something in here that would take time and have a measurable result.
    private static void DoSomething()
    {
        Thread.Sleep(50);
        thingsDone++;
    }

    // These two guys are the data members needed.
    private static int doSomethingCount = 0;
    private static Task doSomethingTask;

    // This code is just to prove that it works the way I expected. You can use it too.
    public static void Run()
    {
        for (int i = 0; i < 10; i++)
        {
            QueueSomething();
        }

        while (thingsDone < 10)
        {
            Thread.Sleep(100);
        }

        thingsDone = 0;
        QueueSomething();

        while (thingsDone < 1)
        {
            Thread.Sleep(100);
        }

        Console.WriteLine("Done");
    }

    // This data point is just so I could test it. Leaving it in so you can prove it yourself.
    private static int thingsDone = 0;
于 2013-06-07T20:45:26.040 回答
0

如果这是纯代码问题,则锁定解决方案很好。但有时您运行一个数据库事务,其中一系列对象(记录)必须在不受干扰的情况下进行修改。很好的例子是当您重新运行数据库记录的序列枚举时。您可以在数据库中创建一个锁定表并锁定其中的特定定义记录,以便在事务中首先更新。这将阻止您的应用程序(在同一代码区域中)创建的其他事务甚至访问您更新的表。第二次通话只会在第一次通话完成后进行。只是一个提示。

于 2013-06-07T20:52:29.480 回答