0

Forgive me in advance. My posting skills are poor. I am learning to post code blocks in a cleaner manner. Please feel free to let me know if it needs formatting.

I have a start point and a set point textbox. I also have a TextBlock that displays the current setpoint value. I would like to increment the value of textblock to increase from start to set point.

ex. start point = 0

set point = 10

I have a Task created:

private async Task MoveZ(double x, double y , double z)
{
    zpos.Text = Convert.ToString(x);
    for (int i = 0; i < z; i++)
    {
        x = x + y;
        await Task.Delay(2000);
        zpos.Text = Convert.ToString(x);
    }
}

I have a button where I call this Task.

private void Button_Click(object sender, RoutedEventArgs e) 
{
    double startpt = Convert.ToDouble(startpoint.Text); 
    double setpt = Convert.ToDouble(setpoint.Text); 
    double steps = Convert.ToDouble(nosteps.Text);
    _ = MoveZ(startpt, setpt, steps);
}

My question is: If I have another textbox where I input 4-- if the loop textblock value reaches 4- then I need to pause it until I change it to 5. The task needs to be paused based on the TextBox input.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
shreyas
  • 13
  • 3
  • By `TextBlock` do you mean `TextBox`? – Theodor Zoulias Oct 31 '20 at 06:10
  • There is a textblock which gives feedback for the setpoint and there is a textbox which is used to pause and resume the task. Input of TextBox needs to match with feedback in textBlock for the task to resume. Until then it should be on pause. – shreyas Oct 31 '20 at 06:21
  • I think you need to user the BackGroundWorker class you will find it documented here https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker?view=netcore-3.1 – Walter Verhoeven Oct 31 '20 at 06:22
  • @WalterVehoeven the `BackgroundWorker` is obsolete. It has been superseded by `Task.Run` and async/await. – Theodor Zoulias Oct 31 '20 at 06:25
  • @TheodorZoulias Thanks for editing the question. I tried using a dispatch timer which monitors textbox and textblock values but I could not find a way to pause a task that is not started if not for the button click. Sort of got to a dead end there. – shreyas Oct 31 '20 at 06:31
  • @TheodorZoulias, I seem to be able to use the Background worker just fine in my .Net 5.0 projects. https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker?view=net-5.0 – Walter Verhoeven Oct 31 '20 at 07:10
  • @WalterVehoeven yes, you can certainly use it if you want to. But it is an awkward and verbose class to use. Why not go with the newer `Task.Run`, which is much more convenient? – Theodor Zoulias Oct 31 '20 at 07:47
  • shreyas I didn't know that a WPF class named `TextBlock` existed. Learning something new every day. Regarding how to pause tasks cooperatively, take a look at [this](https://stackoverflow.com/questions/61089181/restartable-tasks-with-timed-operation/61105033#61105033) or [this](https://stackoverflow.com/questions/61499676/pause-resume-a-thread-in-c-sharp/61502011#61502011) post. It shows how to use the `PauseTokenSource` class. This class is not part of the .NET platform. You must add an external dependency in order to use it. – Theodor Zoulias Oct 31 '20 at 07:56
  • @TheodorZoulias I disagree with your assertion that BackgroundWorker is obsolete; extended/reasonable discussion [here](https://stackoverflow.com/questions/12414601/async-await-vs-backgroundworker) – Caius Jard Oct 31 '20 at 08:40
  • 1
    @CaiusJard thanks for the [link](https://stackoverflow.com/questions/12414601/async-await-vs-backgroundworker). We can agree that we disagree. Personally I would consider using a `BackgroundWorker` for future development only if I was stuck at maintaining a project targeting the [.NET Framework 4.0](https://en.wikipedia.org/wiki/.NET_Framework_version_history#.NET_Framework_4.0) or earlier. – Theodor Zoulias Oct 31 '20 at 08:55

2 Answers2

0

Because MoveZ returns a Task, it should be awaited.

You should not pause a Task or thread or a method in general. Especially when you don't know for how long. Better cancel the Task and restart it. You can always use events to control a "non-linear" flow.
Maybe you should consider to execute MoveZ on a background thread and post the incremented value back to the UI using IProgress<T>:

You need to know: Dependency Property Overview and Data Binding Overview and Async in 4.5: Enabling Progress and Cancellation in Async APIs.

MainWindow.xaml.cs

public static readonly DependencyProperty StopValueProperty = DependencyProperty.Register(
  "StopValue",
  typeof(double),
  typeof(MainWindow),
  new PropertyMetadata(default(double)));

public double StopValue
{
    get => (double) GetValue(MainWindow.StopValueProperty);
    set => SetValue(MainWindow.StopValueProperty, value);
}

public static readonly DependencyProperty StepValueProperty = DependencyProperty.Register(
  "StepValue",
  typeof(double),
  typeof(MainWindow),
  new PropertyMetadata(default(double)));

public double StepValue
{
    get => (double) GetValue(MainWindow.StepValueProperty);
    set => SetValue(MainWindow.StepValueProperty, value);
}

public static readonly DependencyProperty BreakValueProperty = DependencyProperty.Register(
  "BreakValue",
  typeof(double),
  typeof(MainWindow),
  new PropertyMetadata(default(double), MainWindow.OnBreakValueChanged));

public double BreakValue
{
    get => (double) GetValue(MainWindow.BreakValueProperty);
    set => SetValue(MainWindow.BreakValueProperty, value);
}

public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
  "Value",
  typeof(double),
  typeof(MainWindow),
  new PropertyMetadata(default(double)));

public double Value
{
    get => (double) GetValue(MainWindow.ValueProperty);
    set => SetValue(MainWindow.ValueProperty, value);
}

private CancellationTokenSource CancellationTokenSource { get; set; }

public MainWindow()
{
    InitializeComponent();
    this.DataContext = this;
}

private static void OnBreakValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var this_ = d as MainWindow;
    if (this_.Value == this_.BreakValue)
    {
        // Cancel MoveZ()
        this_.CancellationTokenSource.Cancel();
        return;
    }   

    // Task was canceled before --> restart procedure
    if (this_.CancellationTokenSource.IsCancellationRequested)
    {
        this_.StartMoveZ();
    }   
}

private void MoveZ(IProgress<double> progressReporter, CancellationToken cancellationToken)
{
    Task.Run(
    async () =>
    {
        for (; this.Value <= this.StopValue; this.Value += this.StepValue)
        {
            progressReporter.Report(this.Value);
            cancellationToken.ThrowIfCancellationRequested();
            await Task.Delay(2000, cancellationToken);
         }
     }, cancellationToken);
}

private void Button_Click(object sender, RoutedEventArgs e) 
{
    this.Value = 0;
    StartMoveZ();
}

private void StartMoveZ() 
{
    this.CancellationTokenSource = new CancellationTokenSource();
    var progressReporter = new Progress<double>(value => zpos.Text = value);
    MoveZ(progressReporter, this.CancellationTokenSource.Token);
}

MainWindow.xaml

<Window>
  <StackPanel>
    <TextBox Text="{Binding StopValue}" />
    <TextBox Text="{Binding BreakValue}" />
    <TextBox Text="{Binding StepValue}" />
    <Button Content="Start" Click="Button_Click" />
    <TextBlock Text="{Binding Value}" />
  </StackPanel>
</Window>
BionicCode
  • 1
  • 4
  • 28
  • 44
0

You can use Reactive Extensions (Rx) to build queries based on events.

Check FromEvent and FromEventPattern.

Paulo Morgado
  • 14,111
  • 3
  • 31
  • 59