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

时间:2011-08-04 14:30:54

标签: c# .net wpf multithreading task-parallel-library

我是TPL和WPf的新手并且遇到以下问题。 我尝试在无限循环中下载一个站点(这里只是for循环)和 将其添加到队列中。下一个任务将其取出并在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)第83行+ 0x16字节C#       mscorlib.dll!System.Threading.Tasks.Task.InnerInvoke()+ 0x44 bytes       mscorlib.dll!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
      mscorlib.dll!System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()+ 0x7 bytes       mscorlib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch()+ 0x147 bytes
      mscorlib.dll!System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()+ 0x2d bytes
      [原生于管理过渡]

   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似乎会引起问题。没有错误消息。看起来很像一个紧凑的循环。当然,我没弄清楚为什么? 你会这么善良并再次指出我正确的方向吗? 请记住,我已经做了大约6个月的C#和一周的TPL,否则我不会再问你了。但凭借这些经验...... 再次感谢你!

Brians Code:

var task1 = new Task( 
  (obj) => 

为什么需要obj

private void Button1_Click(object sender, RoutedEventArgs e)
        {

TaskScheduler uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();                 BlockingCollection blockingCollection = new BlockingCollection();                 CancellationTokenSource 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);

        }

4 个答案:

答案 0 :(得分:3)

好吧,实际上我只是再次查看你的代码,问题很简单:你正在使用带有状态对象的构造函数重载手动构建一个新的Task实例。没有构造函数重载需要TaskScheduler

通常人们会使用Task.Factory.StartNew,所以我甚至没有注意到您正在手动构建Tasks。手动构造Task实例时,指定调度程序以运行它们的正确方法是使用带有Start实例的TaskScheduler重载。所以在你的情况下你需要这样做:

taskCP.Start(uiTaskScheduler);

<强>更新

好了,所以现在你更新的代码的问题是你已经有效地安排了一个Task在UI(调度程序)线程上坐在那里阅读紧密循环。

现在代码已经过重新设计,很明显你想要在UI线程上安排task2。如果要将通知从那里推送到UI,您可以按照另一个答案的建议调用Dispatcher::BeginInvoke,也可以使用循环内部的uiTaskScheduler启动新任务。调用Dispatcher :: BeginInvoke将减少开销并使代码更清晰恕我直言,所以我建议你这样做。

答案 1 :(得分:2)

您可以使用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

希望它有所帮助。

答案 2 :(得分:2)

您的代码存在一些问题。

  • Task ctor没有超载接受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,然后在访问Dispatcher.Invoke之前使用TxtBlock3

如果您确实要在特定的调度程序上启动Task,请将TaskScheduler传递给Start方法,如下所示。

task1.Start(TaskScheduler.Default);

答案 3 :(得分:0)

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

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