0

I have a XAML application that serves as the UI for an automation. The entire automation can take anywhere from 20-30 hours to fully execute so I created a Task class object that essentially wraps Thread methods (Start/Stop/Reset).

However, when I run the automation method under the Task object, the XAML UI is busy and I cannot interact with the other controls, including the Pause button which toggles the Thread.Set() flag.

There is another post Prevent UI from freezing without additional threads

where someone recommended the BackgroundWorker class this MSDN article mentions it is a bad idea to use this when if it manipulates objects in the UI, which mine does for purposes of displaying status counts: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

Any idea around this?

    private void OnButtonStartAutomationClick(object sender, RoutedEventArgs e)
    {
        btnPauseAutomation.IsEnabled = true;
        Automation.Task AutomationThread = new Automation.Task(RunFullAutomation);
    }

    private void RunFullAutomation()
    {
        // do stuff that can take 20+ hours
        // threaded so I can utilize a pause button (block)
    }

class Task
{
    private ManualResetEvent _shutdownFlag = new ManualResetEvent(false);
    private ManualResetEvent _pauseFlag = new ManualResetEvent(true);
    private Thread _thread;
    private readonly Action _action;

    public Task(Action action)
    {
        _action = action;
    }

    public void Start()
    {
        ThreadStart ts = new ThreadStart(DoDelegatedMethod);
        _thread = new Thread(ts);            
        _thread.Start();
        _thread.Priority = ThreadPriority.Lowest;
    }

    public void Resume()
    {
        _pauseFlag.Set();
    }

    public void Stop()
    {
        _shutdownFlag.Set();
        _pauseFlag.Set();
        _thread.Join();
    }

    private void DoDelegatedMethod()
    {
        do
        {
            _action();
        }
        while (!_shutdownFlag.WaitOne(0));
    }
}
Community
  • 1
  • 1
Wibble
  • 321
  • 1
  • 3
  • 8
  • On a side note - I'd recommend not making a class named `Task`, as it conflicts with the framework's Task class: http://msdn.microsoft.com/en-us/library/system.threading.tasks.task.aspx – Reed Copsey Feb 12 '11 at 02:37

4 Answers4

3

where someone recommended the BackgroundWorker class this MSDN article mentions it is a bad idea to use this when if it manipulates objects in the UI, which mine does for purposes of displaying status counts

BackgroundWorker is actually ideal for this, as it was designed for this type of scenario. The warning is that you shouldn't change UI elements inside of DoWork, but rather via ReportProgress and the ProgressChanged event.

The reason the warning exists is "DoWork" is executed on a background thread. If you set a UI element value from there, you'll get a cross threading exception. However, ReportProgress/ProgressChanged automatically marshals the call back into the proper SynchronizationContext for you.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
1

Take a look at the Dispatcher object in WPF. You can, and should in your scenario, run the long running tasks on a background thread and the BackgroundWorker is a good way to do it. When you need to update the UI you need to verify access to the UI thread and if you don't have it use the dispatcher to invoke an update method on the UI thread.

TheZenker
  • 1,710
  • 1
  • 16
  • 29
1

There are two possible causes here: first, that the blocking task is blocking the UI thread rather than running on a background thread, and second, that the background thread is starving the UI thread so that it never gets the chance to respond to input. You need to find out which of these is the case. A crude way to do this is, in your Click handler, Debug.WriteLine the current thread ID (Thread.CurrentThread.ManagedThreadId), and do the same in the RunFullAutomation callback.

If these print the same number, then you have the first problem. Reed and TheZenker have provided solutions to this.

If these print different numbers, then you are already on a worker thread, and you have the second problem. (BackgroundWorker may get you to the worker thread more elegantly, and will help with updating the UI, but it won't stop starvation.) In this case the simplest fix is probably to set _thread.Priority = ThreadPriority.BelowNormal; before starting the worker thread.

By the way, your code never appears to actually call AutomationThread.Start, which means the RunFullAutomation callback isn't even executed. Is this just a typo?

itowlson
  • 73,686
  • 17
  • 161
  • 157
1

I'd advise against rolling out your own Task class given that .NET 4 has full support for running tasks asynchronously in the background using the Task Parallel Library That said,you can do what Reed suggests and use a BackgroundWorker which is ideal or if you prefer more control over the nature of how the task si executing, you could use the Task class from System.Threading.Tasks and implement something like so:

public partial class MainWindow : Window
{
    CancellationTokenSource source = new CancellationTokenSource();
    SynchronizationContext context = SynchronizationContext.Current;
    Task task;
    public MainWindow()
    {
        InitializeComponent();
    }

    private void DoWork()
    {
        for (int i = 0; i <= 100; i++)
        {
            Thread.Sleep(500); //simulate long running task
            if (source.IsCancellationRequested)
            {
                context.Send((_) => labelPrg.Content = "Cancelled!!!", null);
                break;
            }
            context.Send((_) => labelPrg.Content = prg.Value = prg.Value + 1, null);
        }
    }

    private void Start_Click(object sender, RoutedEventArgs e)
    {
        task = Task.Factory.StartNew(DoWork, source.Token);
    }

    private void Cancel_Click(object sender, RoutedEventArgs e)
    {
        source.Cancel();
    }       
}

In DoWork() you use the WPF SynchronizationContext and post messages to update the UI wiget you need.

The example has a progress bar and a label control that is updated on each iteration of the for loop.Cancellation is supported using CancellationTokenSource which is checked in each iteration.

Hope this helps.

Abhijeet Patel
  • 6,562
  • 8
  • 50
  • 93