我想将数据从一个线程移动到另一个线程,但我的代码仅适用于我传递的第一个值,而不是将前5个值保存到列表中,然后将其打印出来 这是我的代码:
private readonly ConcurrentQueue<int> _queue = new ConcurrentQueue<int>();
private readonly AutoResetEvent _signal = new AutoResetEvent(false);
public void Thread1()
{
List<int> values = new List<int>();
int lastInput;
StringBuilder sb = new StringBuilder();
while (values.Count < 5)
{
_signal.WaitOne();
_queue.TryDequeue(out lastInput);
values.Add(lastInput);
}
for (int i = 0; i < values.Count; i++)
{
sb.Append(String.Format("{0}\n", values[i]));
}
MessageBox.Show(sb.ToString());
}
private void button1_Click(object sender, EventArgs e)
{
Thread th1 = new Thread(Thread1);
th1.Start();
for (int i = 0; i < 8; i++)
{
_queue.Enqueue(i);
_signal.Set();
}
}
答案 0 :(得分:3)
我看到你要做的事情,@ MarcGravell的评论是正确的,以及@mariosangiorgio所说的是真的。您可以做的解决方法是使用Monitor Wait/Pulse
机制。请尝试以下方法:
private readonly Queue<int> _queue = new Queue<int>();
private readonly object _locker = new object();
public void Thread1()
{
List<int> values = new List<int>();
int lastInput;
StringBuilder sb = new StringBuilder();
while (values.Count < 5)
{
lock (this._locker)
{
// wait until there is something in the queue
if (this._queue.Count == 0)
{
Monitor.Wait(this._locker);
}
// get the item from the queue
_queue.Dequeue(out lastInput);
// add the item to the list
values.Add(lastInput);
}
}
for (int i = 0; i < values.Count; i++)
{
sb.Append(String.Format("{0}\n", values[i]));
}
MessageBox.Show(sb.ToString());
}
private void button1_Click(object sender, EventArgs e)
{
Thread th1 = new Thread(Thread1);
th1.Start();
for (int i = 0; i < 8; i++)
{
lock (this._locker)
{
// put something in the queue
_queue.Enqueue(i);
// notify that there is something in the queue
Monitor.Pulse(this._locker);
}
}
}
所以,基本上你要做的就是调用一个循环来尝试总共消耗5个项目。如果消费者线程发现队列中没有要使用的项目,它将等待生产者将一些项目放入队列中。一旦生产者将项目放入队列,它将告诉等待的消费者线程它已准备就绪!然后,消费者线程将解除阻塞并消耗队列中可能存在的任何项目。
另外,如果你考虑@mariosangiorgio评论,你实际上是在使用并发集合。所以他是对的,实际上并不需要阻止。因此,如果您想进行自己的阻止/解除阻止实验,可以使用我的实现并使用常规Queue
(非并发)。或者,就像@mariosangiorgio说的那样,只需删除AutoResetEvent
并让ConcurrentQueue
做其事。
尽管如此,请记住,如果你没有阻止,你将不断循环并运行CPU,直到实际得到Dequeue
'd。
答案 1 :(得分:1)
我以为我会为这个问题提供不同的解决方案。您当然可以使用AutoResetEvent
和ConcurrentQueue
,但这会使代码难以理解,正确并且有理由。
您通常应该尝试使用具有更简单抽象的库。我喜欢微软的Reactive Framework(NuGet&#34; Rx-Main&#34;,&#34; Rx-WinForms&#34;或者&#34; Rx-WPF&#34;)。
Rx允许您创建类似LINQ的操作管道,这些操作是异步执行的,您可以在其中指定您要使用的调度程序(线程)。
这相当于您的代码:
IDisposable subscription =
Observable
.Range(0, 5, Scheduler.Default)
.ToArray()
.Select(xs => String.Join(Environment.NewLine, xs))
.ObserveOn(this)
.Subscribe(x => MessageBox.Show(x));
使用Scheduler.Default
将计算推送到Windows应用程序的新线程。所以字符串生成远离UI线程。 .ObserveOn(this)
将计算推回到UI(因为this
引用当前表单 - 您可以使用任何UI元素而不是this
)。
subscription
是一个IDisposable
,因此您可以随时调用subscription.Dispose()
来缩短计算时间,如果它长时间运行并且您希望它停止。
Rx库非常强大,它为您提供了许多操作符,可以以相对简单的形式执行一些非常复杂的计算。
答案 2 :(得分:0)
我认为问题在于_signal.Set();
和_signal.WaitOne();
。
只有当你在WaitOne
之前总是调用Set
的交错时,这才会按照你想要的方式工作。我怀疑在你的情况下你有以下事件交错:
_signal.WaitOne(); // This waits for the first set
_signal.Set(); // This notifies the first `WaitOne`
_signal.Set(); // This doesn't notify anything
_signal.Set(); // This doesn't notify anything
_signal.Set(); // This doesn't notify anything
_signal.Set(); // This doesn't notify anything
_signal.WaitOne(); // Nothing is going to set this
由于您使用的是ConcurrentQueue
,因此您不需要使用AutoResetEvent
。只需删除它,一切都应该有效。