Home > front end >  Chain tasks using continuation task
Chain tasks using continuation task

Time:11-17

I'm trying to chain tasks, so as soon as the one finishes the next starts, but the UI doesn't update. I did a course in react and the one lesson is where you update the UI based on state changes in the application, and that is what I'm trying to replicate. Change the state of the application (basically I'll be running methods that run return a bool for validation), and then update the UI accordingly, I'm also using binding, but for some reason its not running as intended, I don't know if I follow the documentation incorrectly. What can I change or fix to make this work and is it practically correct to use more than one task in a single async Task<T> method

public async Task<string> Connect_To_Ip()
{
    await Task.Run(() =>
    {
        details.State = "Connection To IP 127.0.01.258.....";
        Task.Delay(5000).Wait();
        }).ContinueWith(result => new Task(async () =>
        {
           await Task.Run(() =>
           {
               if (result.Status == TaskStatus.RanToCompletion)
               {
                  details.State = "Validating Card Number......";
                }                    
           });  
              
        }), TaskContinuationOptions.OnlyOnRanToCompletion);

     return details.State;
}     

How I'm calling the original task

Task connect = Connect_To_Ip();
await connect;

CodePudding user response:

When you use await then you don't need Task.ContinueWith. Everything that follows the awaited operation is a continuation. Since you want to validate on a background thread, you must post the changes back to the UI thread in order to update the UI elements, otherwise you will produce cross-thread exceptions.
This is because UI elements can't be updated from a background thread, except the update occurs via INotifyPropertyChanged and data binding.
One way to do this is to use the Dispatcher to invoke UI manipulations on the UI thread or use the Progress<T> class, which will always execute the registered callback on the UI thread.

Your fixed and simplified code could look like this example:

public async Task ValidateAsync()
{
  // Register the callback that updates the UI with the 'progressReporter'.
  // Progress<T> must be instantiated on the UI thread it is associated with
  var progressReporter = new Progress<string>(message => details.State = message);

  // Execute the operation on a background thread
  await Task.Run(() => ConnectToIp(progressReporter));

  // Continuation starts here, after await
}

public async Task ConnectToIp(IProgress<string> progressReporter)
{
  progressReporter.Report("Connection To IP 127.0.01.258.....");

  await Task.Delay(TimeSpan.FromSeconds(5));

  // Continuation starts here, after await

  progressReporter.Report("Validating Card Number......");
}

It is recommended to use async APIs when possible instead of using background threads. For example, to connect to a server without blocking the UI you can use

HttpClient client = new HttpClient();
HttpResponseMessage response = await client.GetAsync("http://www.contoso.com/");

Many IO classes provide an async API.

Furthermore, I recommend to take a look at the INotifyDataErrorInfo interface. It is the recommended way to implement property validation and allows to provide UI error feedback in a very easy way.

CodePudding user response:

I did this in Windows Forms (I had a test Windows Forms project open), but it should be about the same in WPF. I dropped a button, a label and a text box on the form. Then I wrote this code:

private async void button1_Click(object sender, EventArgs e)
{
    var result = await ValidateTextBox();
    if (result != null)
    {
        label1.Text = result;
        return;
    }
    var intResult = await ReadTextBox();
    label1.Text = intResult.ToString();
    await IncrementTextBox();
    intResult = await ReadTextBox();
    label1.Text = intResult.ToString();
}

private async Task<string> ValidateTextBox()
{
    await Task.Delay(2000);
    if (!int.TryParse(textBox1.Text, out _)) {
        return "Not Valid";
    }
    //otherwise
    return null;
}

private async Task<int> ReadTextBox()
{
    await Task.Delay(3000);
    if (!int.TryParse(textBox1.Text, out var result))
    {
        throw new Exception("Don't do that");
    }
    return result;
}

private async Task IncrementTextBox()
{
    await Task.Delay(3000);
    if (!int.TryParse(textBox1.Text, out var result))
    {
        throw new Exception("Don't do that");
    }
    textBox1.Text = (result   1).ToString();
}

If you type something that's not an int into the text box and press the button, a few seconds go by, and then Not Valid shows up in the label.

If there is a number there, then there is a pause and the number shows up in the label. Then another pause and the text box number will increment by 1. Finally after another pause, the label will show the incremented value.

Note that this all runs on a single thread. But, in spite of all the delays, the UI remains responsive the whole time.

Put breakpoints at the start of each function and on the lines after each of the awaits in the button click handler. Step through (not into) the whole thing and you'll see how the awaits create continuations

  • Related