我目前正在尝试编写多线程WPF c#应用程序,以便UI和其他模块从一开始就在各自的线程上运行。
但是,例如,如果我想将对象从UI传递到模块,我想确保UI不必等待任何事情并且可以立即继续。因此,在这种情况下使用队列不会像其他帖子所建议的那样安静地工作。
有什么建议我可以这样做吗?我正在考虑使用一个连接两个线程的接口类,或者使用一个volatile变量来保存对象等等。但这些都不是真的有效。谢谢
答案 0 :(得分:1)
你不能解释你想做什么,所以很难给出具体的答案。在后台运行一些繁重的工作以避免阻塞UI描述了一半的并发问题。另一半与服务器需要很长时间才能回答时避免长时间等待有关。
如果您想执行一些长时间运行的作业以响应用户的操作,例如点击,只需使用await
和Task.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");