我正在寻找一个类或模式,我可以将void Action
放在任何线程的队列中,还有另一个线程逐个调用队列中的Actions,因此只有一个Action正在运行一次保留队列的顺序。
我已经尝试在操作中使用lock
,但似乎并不能保证订单。
答案 0 :(得分:1)
这是一个简单的示例类,应该或多或少地做你想要的。虽然我没有测试下面的代码,但它基于一个更复杂的版本,我用它代理COM公寓模型中的Func<...>
和Action<...>
调用,这非常有效。无论如何,我希望这能让你足够接近,并帮助你找到一种方法来解决你的问题。
下面的类接受Action
项并将它们放入队列中,然后只要仍有工作要做,就逐个执行。您可以使用System.Collections.Concurrent.ConcurrentQueue<T>
块和lock
来使C#的早期版本无法使用System.Collections.Generic.Queue<T>
。我选择不使用一些较新的事件,以便更容易与其他版本的C#兼容。
如果您还希望使用Action<TArg>
及其所有变体(例如Action<TArg1, TArg2, ...>
,需要为Action<...>
的每个变体编写基类和派生类,还有一些方法可以调整该类。你想支持,派生类本身持有Action<...>
并且还为线程执行它。
该类是线程安全的,因此您可以从任何正在执行的线程中添加Action
。我相信这也应该是安全的,永远不会在队列和睡眠中留下事件,但我不能证明这一点。为了绝对保证,您可以使用包含Interlocked.Increment
,Interlocked.Decrement
和Volatile.Read
的单独项目计数器,而不是检查队列长度,但您仍需要小心操作顺序添加队列项并递增计数器时。
sealed class ActionRunner : IDisposable
{
private ConcurrentQueue<Action> m_queue;
private Thread m_workThread;
private AutoResetEvent m_threadHasWorkEvent;
private ManualResetEvent m_killThreadEvent;
public ActionRunner()
{
m_queue = new ConcurrentQueue<Action>();
m_threadHasWorkEvent = new AutoResetEvent(false);
m_killThreadEvent = new ManualResetEvent(false);
m_workThread = new Thread(new ParameterizedThreadStart(ActionRunnerThread));
m_workThread.IsBackground = true;
m_workThread.Start();
}
private bool disposedValue = false;
private void Dispose(bool disposing)
{
if(!disposedValue)
{
if(m_threadKillEvent != null)
{
m_threadKillEvent.Set();
}
if(m_queue != null)
{
Action dummy;
while(m_queue.TryDequeue(out dummy))
;
}
if(m_workThread != null)
{
m_workThread.Join();
}
disposedValue = true;
}
}
~ActionRunner()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void AddAction(Action toExecute)
{
m_queue.Push(toExecute);
m_threadHasWorkEvent.Set();
}
private void ActionRunnerThread(object o)
{
WaitHandle[] waitList = new WaitHandle[] { m_killThreadEvent, m_threadHasWorkEvent };
while(true)
{
int which = WaitHandle.WaitAny(waitList);
if(which == 0)
{
break;
}
while(m_queue.Count > 0)
{
Action toExecute;
if(!m_queue.TryDequeue(out toExecute))
{
continue;
}
toExecute();
}
}
}
}
使用方法:
ActionRunner myRunner = new ActionRunner();
// void DoWorkHere(); is defined elsewhere
myRunner.AddAction(DoWorkHere);
// This also works
myRunner.AddAction(() => DoWorkHere());
// This works too, but you have to be very careful of closures
// and properly capturing the values of x and y. For example,
// if you change x and y to something else after adding the action,
// there's a good chance the printed result will use the updated
// values and not the original ones.
int x = 5, y = 2;
myRunner.AddAction(() =>
{
// Assuming string AddTwoNumbers(int x, int y) { ... }
System.Diagnostics.Debug.WriteLine(AddTwoNumbers(x, y));
});
// Here's a safer way to do the above without worrying about the
// values being changed. There are many ways to do this with cleaner
// syntax, but this is an easy one.
int x = 5, y = 2;
{
// This copy happens within its own scope and will properly
// capture the values when used in the closure.
int localX = x, localY = y;
myRunner.AddAction(() =>
{
System.Diagnostics.Debug.WriteLine(AddTwoNumbers(localX, localY));
});
}
// When you're done, Dispose the class so it stops running the thread
myRunner.Dispose();
答案 1 :(得分:0)
如果您正在使用TPL,您可以创建自己的TaskScheduler
版本来限制并发性,然后您就可以在同一个调度程序上排队每个任务。
static void Main()
{
LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(1);
TaskFactory factory = new TaskFactory(lcts);
factory.StartNew(()=>
{
for (int i = 0; i < 500; i++)
{
Console.Write("{0} on thread {1}", i, Thread.CurrentThread.ManagedThreadId);
}
}
);
Console.ReadKey();
}
请参阅 - https://msdn.microsoft.com/en-us/library/ee789351(v=vs.100).aspx