多次调用(一次)到一次只能运行一个的方法

时间:2013-06-07 20:28:59

标签: c# oop

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

public void DoSomething() {}

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

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

private void DoSomething() {}

我正试图以优雅的方式解决这个问题。我的问题是DoSomething()知道如何运行。我想过创建一个计时器来检查队列,但是它会全天候运行,因为每年可能发生两次。

另一个想法是让DoSomething()在完成其他人订阅的事件时触发事件,从队列中选择一些工作,然后调用DoSomething()。还有更好的方法吗?

5 个答案:

答案 0 :(得分: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

答案 1 :(得分: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);
    }
}

答案 2 :(得分:1)

这样做的简单方法是使用MethodImplOptions.Synchronized来修饰方法,其功能类似于Java中的synchronized关键字:

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

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

答案 3 :(得分: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;

答案 4 :(得分:0)

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