开发者

WPF, TPL, Producer/Consumer Pattern - Wrong Thread Error

开发者 https://www.devze.com 2023-03-25 01:54 出处:网络
I\'m new to TPL and WPf and have the following problem. I try to download a site in an infinite loop (here only a for loop) and

I'm new to TPL and WPf and have the following problem. I try to download a site in an infinite loop (here only a for loop) and add it to a Queue. The next Task takes it out and shows it in the Textblock. However I don't seem to get the right Thread for the UI although I think that I use the TaskScheduler properly.

Thank you for any help!

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 bytes C# mscorlib.dll!System.Threading.Tasks.Task.InnerInvoke() + 0x44 bytes mscorlib.dll!System.Threading.Tasks.Task.Execute() + 0x43 bytes 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

[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: 

Thank you alot for all of your help. I still have two question: I rewrote my code a little bit with the Task.Factory.StartNew. However my Task2 seems to cause problems. There's no error message. Seems rather like a tight loop. Of course, I didn’t figure out why? Would you be so kind and point me into the right direction again. Keep in mind that I’ve been doing C# for ~6 months and TPL for a week otherwise I wouldn’t ask you again. But with this amount of experience ... Thank you again!

Brians Code:

var task1 = new Task( 
  (obj) => 

Why is obj needed?

private void Button1_Click(object sender, RoutedEventArgs e)
        {

TaskScheduler uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); BlockingCollection blockingCollection = new BlockingCollection(); CancellationTokenSource cts = new CancellationTokenSource();

            Cance开发者_开发问答llationToken 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);

        }


Ok, actually I just looked at your code again and the problem is simple: You're constructing a new Task instances manually using the constructor overload that takes a state object. There is no constructor overload that takes a TaskScheduler.

Normally people use Task.Factory.StartNew, so I didn't even notice that you were constructing the Tasks manually. When you construct Task instances manually, the proper way to specify the scheduler to run them on is to use the Start overload that takes a TaskScheduler instance. So in your case you would need to do:

taskCP.Start(uiTaskScheduler);

UPDATE

Ok, so now the problem with your updated code is you've effectively scheduled a Task on UI (dispatcher) thread that is sitting there reading in a tight loop.

Now that the code is reworked, it becomes clear that you do not want to schedule task2 on the UI thread. If you want to push out notifications from there to the UI you can either call Dispatcher::BeginInvoke as suggested by another answer or you can start a new Task using the uiTaskScheduler inside of the loop. Calling Dispatcher::BeginInvoke will have less overhead and be clearer code wise IMHO, so I recommend just doing that.


You can use Dispatcher to access UI thread like:

Dispatcher.BeginInvoke(new Action(()=>{TxtBlock2.Text = "Signalling production end";}));

Because a task runs in another thread other than UI thread, Dispatcher gives you a changes to access the thread which your UI at. MSDN gives a good explanation about it, please view the remark part at: http://msdn.microsoft.com/en-us/library/system.windows.threading.dispatcher.aspx

Hope it helps.


There are a couple of problems with your code.

  • There is no overload of the Task ctor that accepts a TaskScheduler. What you have actually done is passed the TaskScheduler to the state parameter that is then picked in your obj variable on the lambda expression.
  • Because of the point above taskCP is actually running on the default scheduler and not uiScheduler.
  • Because of the point above taskCP is attempting to access a UI element from a non-UI thread by modifying TxtBlock3.
  • Likewise task1 is attemping the same by modifying TxtBlock2.

Here is how I would refactor the code.

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

Notice that ContinueWith can accept a TaskScheduler and that is exactly what I have done above. I also have taskCP running on the default scheduler and then I use Dispatcher.Invoke before accessing TxtBlock3.

If you really want to start a Task on a specific scheduler then pass a TaskScheduler to the Start method like the following.

task1.Start(TaskScheduler.Default);


Use Dispatcher.Invoke() to invoke the code that use on UI elements from different thread. For example

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

精彩评论

暂无评论...
验证码 换一张
取 消