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.
答案 0 :(得分: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.
答案 1 :(得分: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
}
}
答案 2 :(得分: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
尝试这种方法 - 如果操作时间超过2秒,它将只显示对话窗口。你也可以将所有这些包装在另一个任务中,然后调用者可以等待整个事情,无论对话框是否显示都没有区别。