2

I am executing a potentially long running operation in the background thread of a modal dialog. The problem is that, when the operation takes a short time, the dialog is shown and closed almost instantaneously, which annoys the users. I would like to show the dialog only if the operation takes longer than, say, 2s.

The dialog is a WPF Window and the long running operation code is in the ViewModel. The ViewModel creates a Task that runs the operation in the background.

Here is a relevant snippet:

public Task StartAction() {
    var mainTask = Task.Factory.StartNew(InternalAction);
    MainTask = mainTask;
    mainTask.ContinueWith(_ => { IsFinished = true; });
    return mainTask;
}   

InternalAction is the potentially long running operation.

This is how I am trying to introduce the delay. I am using Sriram Sakthivel's suggestions from a different answer, but the code is not exactly the same:

var viewModel = ... // Creates the ViewModel
var dialogWindow = ... // Creates the Window and starts the operation by calling viewModel.StartAction();

var delayTask = Task.Delay(2000);
if (viewModel.MainTask != null) {
    Task.WaitAny(delayTask, viewModel.MainTask);
}

if (viewModel.IsFinished) {
    return;
}

ShowDialog(dialogWindow); // this code calls dialogWindow.ShowDialog() eventually

I am not using await because I do not want to yield control to the caller (COM) because the caller expects the result to be ready when it gets the control back.

I have been experimenting with different timeouts, e.g., 5000ms, and I do not see any difference in the behavior. The dialog windows still "blink" (are shown and closed immediately). I am sure I am doing something wrong, but I cannot understand my mistake.

Developer
  • 435
  • 4
  • 16

3 Answers3

0

You're waiting on MainTask, but MainTask isn't the task that sets IsFinished. You may be returning from WaitAny after InternalAction completes but before the IsFinished = true continuation completes.

Try setting MainTask to the continuation rather than its antecedent:

public Task StartAction() {
    var mainTask = Task.Factory.StartNew(InternalAction);
    var continuation = mainTask.ContinueWith(_ => { IsFinished = true; });

    MainTask = continuation;

    return mainTask;
}  

Note that continuation cannot begin until mainTask has completed, so with this change you'll be waiting on mainTask and continuation.

Note, however, that if IsFinished is being read from the UI thread, you'll want to also set it from the UI thread. That, or make it backed by a volatile field.

Mike Strobel
  • 25,075
  • 57
  • 69
0

There used to be a 3rd party Library called "Busy Indicator". Maybe you could enable it to only appear if the busy condition is met for a certain time? (https://github.com/xceedsoftware/wpftoolkit/wiki/Xceed-Toolkit-Plus-for-WPF).

Basically it comes down to the ViewModel exposing a "busy" property (or any property that can be converted into a boolean value representing "busy"). And the View reacting to the change on a delay (if any).

I am not sure if XAML itself can do that, as you need to show a window. A bit of code behind might be nesseary here. How about you register a custom ChangeNotification handler that starts a timer, with the timer re-checking if the condition is still met in the "tick" event?

Here is some code, made largely from memory:

//custom ChangeNofiticationHander
busyChangeHanlder(object sender, PropertyChangedEventArgs e){
  if(e.PropertyName == "BusyBoolean"){
    if(BusyBoolean)
      //Start the timer
    else
      //Stop the timer
  }
}

timerTickHandler(object sender, TimerTickEventArgs e){
  if(BusyBoolean){
     //create and Dispaly the Dialog here
  }
}
Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
Christopher
  • 9,634
  • 2
  • 17
  • 31
0
        var mainTask = Task.Delay(5000); // your long running task

        if(Task.WaitAny(mainTask, Task.Delay(2000)) == 1){ // if the delay enden first, show dialog
            showDialog();
            await mainTask;
            closeDialog();
        }
        await mainTask; // this will just skip, if mainTask is already done

Try this approach - it will only show dialog window, if the operation takes longer that 2s. You can also wrap all that in another task, then the caller can await the whole thing with no difference whether the dialog was shown or not.

Krzysztof Skowronek
  • 2,796
  • 1
  • 13
  • 29