不使用Queue将对象传递给不同的线程

时间:2017-07-13 07:48:23

标签: c# multithreading

我目前正在尝试编写多线程WPF c#应用程序,以便UI和其他模块从一开始就在各自的线程上运行。

但是,例如,如果我想将对象从UI传递到模块,我想确保UI不必等待任何事情并且可以立即继续。因此,在这种情况下使用队列不会像其他帖子所建议的那样安静地工作。

有什么建议我可以这样做吗?我正在考虑使用一个连接两个线程的接口类,或者使用一个volatile变量来保存对象等等。但这些都不是真的有效。谢谢

3 个答案:

答案 0 :(得分:1)

你不能解释你想做什么,所以很难给出具体的答案。在后台运行一些繁重的工作以避免阻塞UI描述了一半的并发问题。另一半与服务器需要很长时间才能回答时避免长时间等待有关。

如果您想执行一些长时间运行的作业以响应用户的操作,例如点击,只需使用awaitTask.Run

public async void myButton_Click(object sender, EventArguments arg)
{
    ....
    var result=await Task.Run(()=>calculate(someData);
    toolStripLabel1.Text ="Step 1 complete";
    var result2=await Task.Run(()=>someOtherCalculation(result));
    txtBox1.Text=result2;
}

这将使用来自线程池的后台线程来运行计算并释放UI线程。计算完成后,将在UI线程中使用await ...调用之后的语句继续执行。

async/await可用于对数据库,服务器和文件进行异步 IO调用。在这种情况下,没有后台处理。在等待服务器或磁盘响应时,不会阻塞,而是在异步调用完成时释放UI线程并恢复执行:

public async void myButton_Click(object sender, EventArguments arg)
{
    var client=new HttpClient();
    ....
    var result=await client.GetStringAsync(someUrl);
    txtBox1.Text=result;
}

在许多情况下,您希望将一些数据排队以进行最终处理,例如从多个线程或任务写入日志文件。或者,您可能有一个线程生成另一个线程/任务需要使用的数据而不会阻塞原始线程。

在这种情况下可以使用许多技术和类。

也许最直接的方法是使用TPL Dataflow库中的ActionBlock类。它允许其他线程异步发送消息,它使用一个或多个任务缓冲数据并处理消息。默认情况下,仅使用一个任务:

var logBlock= new ActionBlock<string>(msg=>File.AppendLine("log.txt",msg));

logBlock.Post(&#34;你好&#34;!);

使用例如10个并发任务的ActionBlock可以用于例如同时发送多个HTTP请求

HttpClient client=new HttpClient();

var options=new ExecutionDataflowBlockOptions{MaxDegreeOfParallelism = 10};
var downloadBlock= new ActionBlock<Tuple<string,string>>(async msg=>{
    var content=await client.GetStringAsync(msg.Item1);
    await File.AppendText(msg.Item2,content));
}

downloadBlock.Post(Tuple.Create(someUrl,someFile));

可以指定更多选项,例如BoundedCapacity对可以排队的消息数量设置上限,如果生产者/海报太快则防止溢出。

ActionBlock是TPL Dataflow库的一部分。另一个有趣的块是TransformBlock,它在输出缓冲区中返回一个结果。可以链接多个块,以便每个块在不同的线程中执行处理步骤。

另一种常见情况是生产者/消费者。在这种情况下,一个线程产生数据,将它放置在某个地方&#34;另一个线程消耗并处理该数据。

某处可以是ConcurrentQueue< T>,它是.NET的并发集合之一。这些类是线程安全的,这意味着多个线程可以同时读取和写入它们而不会有损坏的风险。

var queue = new ConcurrentQueue<string>(10);

var producer = Task.Run(async ()=>{
        for(i=0;i<100;i++)
        {
            queue.Enqueue($"Message {i}");
            await Task.Delay(100);
        }
    });

var consumer = Task.Run(()=> {
    while(true)
    {
        if (queue.TryDequeue(var out msg))
        {
            File.AppendLine("log.txt",msg);
        }
    }
});

如果没有消息,您可以使用BlockingCollection方法阻止BufferBlock< T>来避免轮询。在内部,它使用ConcurrentQueue,虽然可以更改为例如ConcurrentStack:

Take

最后,来自TPL Dataflow的{{3}}类提供了var queue = new BlockingCollection<string>(10); var producer = Task.Run(async ()=>{ for(i=0;i<100;i++) { queue.Add($"Message {i}"); await Task.Delay(100); } queue.CompleteAdding(); }); var consumer = Task.Run(()=> { while(!queue.IsCompleted) { var msg=queue.Take(); File.AppendLine("log.txt",msg); } }); ,因此无需阻止等待消息:

ReceiveAsync

答案 1 :(得分:0)

您可以使用SyncronizationContext来执行此操作。 Here是如何使用它的示例。希望它有所帮助。

答案 2 :(得分:0)

我认为不使用队列的论点并不牢固。您必须确保最小化锁定的代码/时间。

我为它编写了一个很好的类,它也支持批处理。你可以检查它并可能给予灵感:

<强>的WorkerThread:

// A worker thread that will run an action and batchup items.
public class WorkerThread<T> : IDisposable
{
    private Thread _thread;
    private List<T> _workItems = new List<T>();

    private ManualResetEvent _terminating = new ManualResetEvent(false);
    private AutoResetEvent _hasItems = new AutoResetEvent(false);

    private DateTime _lastExceedMessageDisplayed = DateTime.MinValue;

    public WorkerThread(Action<T[]> action, int maxBatchSize = 16, int queueLengthWarning = 1000, double queueLengthExceedMessageTimeoutSeconds = 2, ThreadPriority threadPriority = ThreadPriority.Normal)
    {
        if (action == null)
            throw new ArgumentNullException("action");

        if (maxBatchSize < 1)
            throw new ArgumentOutOfRangeException("maxBatchSize", "must be higher than 0");

        if (queueLengthWarning < 1)
            throw new ArgumentOutOfRangeException("queueLengthWarning", "must be higher than 0");

        _thread = new Thread(() =>
        {
            try
            {
                var handles = new EventWaitHandle[] { _terminating, _hasItems };

                while (true)
                {
                    // wait if one of the handles is set.
                    int index = EventWaitHandle.WaitAny(handles);

                    // create copy of queue
                    T[] items;

                    int totalQueueLength;
                    lock (_workItems)
                    {
                        totalQueueLength = _workItems.Count;
                        items = _workItems.Take(maxBatchSize).ToArray();
                        _workItems.RemoveRange(0, items.Length);

                        if (totalQueueLength > items.Length)
                            _hasItems.Set();
                    }

                    if (items.Length > 0)
                    {
                        if ((totalQueueLength > queueLengthWarning)
                            && (_lastExceedMessageDisplayed.AddSeconds(queueLengthExceedMessageTimeoutSeconds) < DateTime.UtcNow))
                        {
                            var msg = "WorkerThread: Queue length exceed limit " + queueLengthWarning + " with " + totalQueueLength;
                            Trace.TraceWarning(msg);
                            _lastExceedMessageDisplayed = DateTime.UtcNow;
                        }

                    }

                    // check if the terminating is set and not actions are queued..
                    if (_terminating.WaitOne(0) && (items.Length == 0))
                        break;

                    action(items);
                }

            }
            catch (Exception exception)
            {
                var msg = exception.ToString();
                Trace.TraceError(msg);
            }

        });
        _thread.IsBackground = true;
        _thread.Priority = threadPriority;
        _thread.Start();
    }

    public bool Add(T item)
    {
        // do not add new items when terminating
        if (_terminating.WaitOne(0))
            return false;

        lock (_workItems)
        {
            _workItems.Add(item);
            _hasItems.Set();
        }
        return true;
    }

    public bool AddRange(IEnumerable<T> items)
    {
        // do not add new items when terminating
        if (_terminating.WaitOne(0))
            return false;

        lock (_workItems)
        {
            _workItems.AddRange(items);
            _hasItems.Set();
        }
        return true;
    }

    public void Dispose()
    {
        _terminating.Set();
        _thread.Join();
    }
}

<强>用法:

// create the workerthread
_workerThread = new WorkerThread<Message>(
    (messages) =>
    {
        using (var writer = File.AppendText("myFile.log"))
            foreach (var message in messages)
                writer.WriteLine(message);
    });


_workerThread.Add("Hi there");
_workerThread.Add("Second line");