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);
        }

WPF, TPL,生产者/消费者模式-错误的线程错误

好吧,实际上我只是再次看了你的代码,问题很简单:你正在手动构建一个新的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,那么像下面这样传递一个TaskSchedulerStart方法。

task1.Start(TaskScheduler.Default);

使用Dispatcher.Invoke()调用来自不同线程的UI元素上使用的代码。例如

string dlCode;
if (blockingCollection.TryTake(out dlCode))
{       
    Dispatcher.Invoke(() =>
    {
         TxtBlock3.Text = dlCode; 
    }
}