WPF, TPL,生产者/消费者模式-错误的线程错误
本文关键字:错误 线程 模式 消费者 TPL 生产者 WPF | 更新日期: 2023-09-27 18:04:25
我是TPL和WPf的新手,有以下问题。我尝试下载一个网站在无限循环(这里只有一个for循环)和将其添加到Queue中。下一个Task将它取出并显示在Textblock中。然而,我似乎没有得到正确的线程的UI,虽然我认为我使用TaskScheduler正确。
谢谢你的帮助!
BlockingCollection<string> blockingCollection = new BlockingCollection<string>();
CancellationToken token = tokenSource.Token;
TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
Task task1 = new Task(
(obj) =>
{
for (int i = 0; i < 10; i++)
{
if (token.IsCancellationRequested)
{
TxtBlock2.Text = "Task cancel detected";
throw new OperationCanceledException(token);
}
else
{
string code = i.ToString() + "'t" + AsyncHttpReq.get_source_WebRequest(uri);
blockingCollection.Add(code);
}
}
}, TaskScheduler.Default);
task1.ContinueWith(antecedents =>
{
TxtBlock2.Text = "Signalling production end";
blockingCollection.CompleteAdding();
}, uiScheduler);
Task taskCP = new Task(
(obj) =>
{
while (!blockingCollection.IsCompleted)
{
string dlCode;
if (blockingCollection.TryTake(out dlCode))
{
//the calling thread cannot access this object because a different thread owns it.
TxtBlock3.Text = dlCode;
}
}
}, uiScheduler);
WindowsBase.dll!System.Windows.Threading.Dispatcher.VerifyAccess() + 0x4a bytes
WindowsBase.dll!System.Windows.DependencyObject.SetValue(System.Windows.DependencyProperty dp, object value) + 0x19 bytes
PresentationFramework.dll!System.Windows.Controls.TextBlock.Text.set(string value) + 0x24 bytes
WpfRibbonApplication4.exe ! WpfRibbonApplication4.MainWindow.Button1_Click。AnonymousMethod__4(object obj) Line 83 + 0x16字节c#System.Threading.Tasks.Task.InnerInvoke() + 0x44字节System.Threading.Tasks.Task.Execute() + 0x43字节mscorlib.dll ! System.Threading.Tasks.Task。ExecutionContextCallback(object obj) + 0x27 bytes
mscorlib.dll ! System.Threading.ExecutionContext.Run (System.Threading。ExecutionContext ExecutionContext, System.Threading.ContextCallback, callback, object state, bool ignoreSyncCtx) + 0xb0 bytes
mscorlib.dll ! System.Threading.Tasks.Task。ExecuteWithThreadLocal(ref System.Threading.Tasks.Task currentTaskSlot) + 0x154 bytes
mscorlib.dll ! System.Threading.Tasks.Task。ExecuteEntry(bool bPreventDoubleExecution) + 0x8b bytes
System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() + 0x7字节System.Threading.ThreadPoolWorkQueue.Dispatch() + 0x147 bytes
System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() + 0x2d bytes
[Native to Managed Transition]
System.InvalidOperationException was unhandled by user code
Message=The calling thread cannot access this object because a different thread owns it.
Source=WindowsBase
StackTrace:
at System.Windows.Threading.Dispatcher.VerifyAccess()
at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
at System.Windows.Controls.TextBlock.set_Text(String value)
at WpfRibbonApplication4.MainWindow.<>c__DisplayClass5.<Button1_Click>b__3(Object o) in C:' ... 'WpfRibbonApplication4'WpfRibbonApplication4'MainWindow.xaml.cs:line 90
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
InnerException:
非常感谢你的帮助。我还有两个问题:我用Task.Factory.StartNew稍微重写了一下代码。然而,我的Task2似乎引起了问题。没有错误信息。看起来像是一个紧密的循环。当然,我不知道为什么?你能不能再给我指出正确的方向?请记住,我做c#已经有6个月了,做TPL也只有一周了,否则我不会再问你了。但有了这么多的经验…再次感谢!
布莱恩斯代码:
var task1 = new Task(
(obj) =>
为什么需要obj
?
private void Button1_Click(object sender, RoutedEventArgs e)
{
TaskScheduler uiTaskScheduler = taskschedule . fromcurrentsynchronizationcontext ();BlockingCollection = new BlockingCollection();cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task task1 = Task.Factory.StartNew(
() =>
{
for (int i = 0; i < 10 ; i++)
{
token.ThrowIfCancellationRequested();
string code = i++.ToString() + "'t" + AsyncHttpReq.get_source_WebRequest(uriDE);
blockingCollection.Add(code);
}
}, token, TaskCreationOptions.None, TaskScheduler.Default);
task1.ContinueWith(
(antecedents) =>
{
if (token.IsCancellationRequested)
{
TxtBlock2.Text = "Task cancel detected";
}
else
{
TxtBlock2.Text = "Signalling production end";
}
blockingCollection.CompleteAdding();
}, uiTaskScheduler);
Task task2 = Task.Factory.StartNew(
() =>
{
while (!blockingCollection.IsCompleted)
{
string dlcode;
if (blockingCollection.TryTake(out dlcode))
{
TxtBlock3.Text = dlcode;
}
}
}, token, TaskCreationOptions.None, uiTaskScheduler);
}
好吧,实际上我只是再次看了你的代码,问题很简单:你正在手动构建一个新的Task
实例,使用构造函数重载,它接受一个状态对象。没有带TaskScheduler
的构造函数重载。
通常人们使用Task.Factory.StartNew
,所以我甚至没有注意到您正在手动构建Tasks
。当您手动构造Task
实例时,指定运行它们的调度器的正确方法是使用Start
重载,它接受一个TaskScheduler
实例。在你的例子中,你需要输入:
taskCP.Start(uiTaskScheduler);
好的,现在你更新的代码的问题是,你已经有效地安排了一个Task
在UI(调度器)线程,坐在那里在一个紧密的循环中读取。
现在代码被重写了,很明显,并不是想在UI线程上调度task2。如果你想从那里推送通知到UI,你可以调用Dispatcher::BeginInvoke
,正如另一个答案所建议的那样,或者你可以在循环内部使用uiTaskScheduler启动一个新任务。调用Dispatcher::BeginInvoke会有更少的开销和更清晰的代码,所以我建议这样做。
您可以使用Dispatcher访问UI线程,如:
Dispatcher.BeginInvoke(new Action(()=>{TxtBlock2.Text = "Signalling production end";}));
因为任务在UI线程之外的另一个线程中运行,Dispatcher给你一个访问UI所在线程的更改。MSDN对此给出了很好的解释,请查看注释部分:http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx
希望能有所帮助。
你的代码有几个问题。
- 没有接受
TaskScheduler
的任务项过载。您实际所做的是将TaskScheduler
传递给state
参数,然后在lambda表达式上的obj
变量中选择该参数。 - 因为上面的
taskCP
实际上是在默认调度程序上运行,而不是uiScheduler
。 - 因为上面的
taskCP
试图通过修改TxtBlock3
从非UI线程访问UI元素。 - 同样
task1
也试图修改TxtBlock2
。
下面是我将如何重构代码。
var queue = new BlockingCollection<string>();
var cts = new CancellationTokenSource();
TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext();
var task1 = new Task(
() =>
{
for (int i = 0; i < 10; i++)
{
token.ThrowIfCancellationRequested();
string code = i.ToString() + "'t" + AsyncHttpReq.get_source_WebRequest(uri);
queue.Add(code);
}
});
task1.ContinueWith(
antecedents =>
{
if (token.IsCancellationRequested)
{
TxtBlock2.Text = "Task cancel detected";
}
else
{
TxtBlock2.Text = "Signalling production end";
}
queue.CompleteAdding();
}, ui);
var taskCP = new Task(
() =>
{
while (!queue.IsCompleted)
{
string dlCode;
if (queue.TryTake(out dlCode))
{
Dispatcher.Invoke(() =>
{
TxtBlock3.Text = dlCode;
}
}
}
});
task1.Start();
taskCP.Start();
注意ContinueWith
可以接受TaskScheduler
,这正是我上面所做的。我还在默认调度器上运行taskCP
,然后在访问TxtBlock3
之前使用Dispatcher.Invoke
。
如果你真的想在一个特定的调度程序上启动一个Task
,那么像下面这样传递一个TaskScheduler
给Start
方法。
task1.Start(TaskScheduler.Default);
使用Dispatcher.Invoke()
调用来自不同线程的UI元素上使用的代码。例如
string dlCode;
if (blockingCollection.TryTake(out dlCode))
{
Dispatcher.Invoke(() =>
{
TxtBlock3.Text = dlCode;
}
}