在单独的线程中按顺序处理操作

时间:2017-06-09 07:05:03

标签: c# .net multithreading

我正在寻找一个类或模式,我可以将void Action放在任何线程的队列中,还有另一个线程逐个调用队列中的Actions,因此只有一个Action正在运行一次保留队列的顺序。

我已经尝试在操作中使用lock,但似乎并不能保证订单。

2 个答案:

答案 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.IncrementInterlocked.DecrementVolatile.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