我有一个业务逻辑方法,必须先完成才能再次调用。多个客户端可以立即调用它:
public void DoSomething() {}
我正在考虑通过将方法设为私有来解决它,并创建一个新的公共方法来将请求放在队列中:
public void QueueSomeWork()
{
// put on a Queue object
// How will DoSomething get invoked now?
}
private void DoSomething() {}
我正试图以优雅的方式解决这个问题。我的问题是DoSomething()
知道如何运行。我想过创建一个计时器来检查队列,但是它会全天候运行,因为每年可能发生两次。
另一个想法是让DoSomething()
在完成其他人订阅的事件时触发事件,从队列中选择一些工作,然后调用DoSomething()
。还有更好的方法吗?
答案 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中创建一个锁定表,并在其中锁定特定的已定义记录,以便在事务中进行更新。这将阻止您的应用程序(在相同的代码区域中)创建的其他事务甚至到达您更新的表。第二次通话只会在第一次通话完成后进行。只是一个提示。