-2

I have an existing system with several long running operations. I was hoping to find a simple way to wrap these operations in tasks and give the user a busy status notification.

This is using .net 3.5 and I am using the TPL back port. Unfortunately async/await cannot be used here.

I've tried to come up with solution in a simple test form shown below and have run into some problems wrapping my head around this.

The below method only displays the first 2-3 tasks, after that UI doesn't get updated until all are complete. Why is this?

private void RunTaskUpdateBefore (string msg)
{
UpdateUI (msg);
Task task = Task.Factory.StartNew (() =>
{
    // simulate some work being done
    Thread.Sleep (1000);
});
task.Wait ();  
}

I was hoping this method would work, but doesn't update UI until all tasks are completed

private void ChildTaskExternalWait (string msg)
{
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext ();

Task task = Task.Factory.StartNew (() =>
{
    Task.Factory.StartNew (() =>
    {
        UpdateUI (msg);
    }, System.Threading.CancellationToken.None, TaskCreationOptions.None, uiScheduler);

    // simulate some work being done
    Thread.Sleep (1000);
});
task.Wait (); // I assume this is the problem
}

The below method works great but I'm not sure if I can use it in my project. It is a fairly large complicated system, the operations happen in many different classes and are controlled by a legacy system that we can't modify. I'd assume I'd have to try to create some sort of global tasking queue to do this?

private Task RunTaskInternalWaitOnPrevious (string msg, Task t1 = null)
{
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext ();
Task task = Task.Factory.StartNew (() =>
{
    if (t1 != null)
        {
        t1.Wait ();
        }
    Task.Factory.StartNew (() =>
    {
        UpdateUI (msg);
    }, System.Threading.CancellationToken.None, TaskCreationOptions.None, uiScheduler);

    // simulate some work being done
    Thread.Sleep (2000);
});

return task;
}

My test form implementation:

private void button1_Click (object sender, EventArgs e)
{
// Only displays the first 2-3 tasks. After that UI doesn't get updated until all are complete
RunTaskUpdateBefore ("Task 1");
RunTaskUpdateBefore ("Task 2");
RunTaskUpdateBefore ("Task 3");
RunTaskUpdateBefore ("Task 4");
RunTaskUpdateBefore ("Task 5");
RunTaskUpdateBefore ("Task 6");
RunTaskUpdateBefore ("Task 7");

// this works with any number of tasks
// Unfortunately I'm not sure if I can use it in my application (see above notes)
//var t1 = RunTaskInternalWaitOnPrevious ("Task 1");
//var t2 = RunTaskInternalWaitOnPrevious ("Task 2", t1);
// and so on 
}
private void UpdateUI (string msg)
    {
    this.textBox1.Text += msg;
    this.textBox1.Text += System.Environment.NewLine;
    }

update/clarification

*My tasks can not run in parallel.
*I want to inform the user that work is being done at each step.
*No other UI input can happen while the tasks are running.

The tasks are not centralized to a particular piece of code and happen all over the place. I'm not sure how else to describe my situation, sorry.

kbeal2k
  • 672
  • 1
  • 5
  • 20

3 Answers3

2

In this code sample, button1_Click runs synchronously and will block the UI thread until it is complete, so even though the Tasks created in the method it is calling want to update the UI thread, I suspect those attempts are being queued until after everything completes. To your point, await and async were designed to solve this problem. Prior to this being available, BackgroundWorkers could be used to do the same. I would add a BackgroundWorker to your form, move all of the code from button1_Click to the BackgroundWorker's DoWork method, and start the background worker when Button1 is clicked. I believe that BackgroundWorkers are deprecated, but work fine with .Net 3.5

Here is a quick tutorial on the BackgroundWorker class if you are unfamiliar with it: https://www.dotnetperls.com/backgroundworker

Inserting Application.DoEvents to pause the execution of button1_Click until the UI can update itself is another option, but this approach has many potential problems. See this thread for your long list of fair warnings: Use of Application.DoEvents()

Community
  • 1
  • 1
Dave Smash
  • 2,941
  • 1
  • 18
  • 38
0

When running multiple threads that provide progress information you want to display, you could proceed as follow:

  • define an UpdateInfo class containing the task id and the progress information,
  • create a variable UpdateInfos = new List< UpdateInfo >(),
  • when you want to send progress information from a task, create and fill an UpdateInfo, then push it on the list, i.e. Lock(UpdateInfos) { UpdateInfos.Add(...) ;}
  • create a timer (e.g. every 250ms) in main form and, in the timer event, pop all the UpdateInfo and refresh the HMI accordingly.
Graffito
  • 1,658
  • 1
  • 11
  • 10
-1

You can use continueWith to update the thread right after task is done.

var task1 = new Task(() => Thread.Sleep(1000)); //replace with actual task

var task2 = task1.ContinueWith(t => UpdateUI(msg), CancellationToken.None, TaskContinuationOptions.None, uiScheduler);

task1.Start();
Keith Hall
  • 15,362
  • 3
  • 53
  • 71