【问题标题】:WPF: display a progress dialog only when its background task takes longer than a specified intervalWPF:仅当其后台任务花费的时间超过指定时间间隔时才显示进度对话框
【发布时间】:2023-04-09 21:40:01
【问题描述】:

我正在模态对话框的后台线程中执行一个可能长时间运行的操作。问题是,当操作需要很短的时间时,对话框几乎立即显示和关闭,这让用户很恼火。 我只想在操作时间超过 2 秒时才显示对话框。

对话框是一个 WPF 窗口,长时间运行的操作代码在 ViewModel 中。 ViewModel 创建一个在后台运行操作的任务。

这是一个相关的sn-p:

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

InternalAction 是可能长时间运行的操作。

这就是我试图引入延迟的方式。我正在使用来自不同 answer 的 Sriram Sakthivel 的建议,但代码并不完全相同:

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

我没有使用await,因为我不想将控制权交给调用者 (COM),因为调用者希望在获得控制权时结果准备就绪。

我一直在尝试不同的超时时间,例如 5000 毫秒,但我没有发现行为有任何差异。对话窗口仍然“闪烁”(显示并立即关闭)。我确定我做错了什么,但我无法理解我的错误。

【问题讨论】:

    标签: c# wpf mvvm task


    【解决方案1】:

    您正在等待MainTask,但MainTask 不是设置IsFinished 的任务。您可能会在InternalAction 完成之后从WaitAny 返回,但IsFinished = true 延续完成之前。

    尝试将MainTask 设置为延续而不是其先行:

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

    请注意,continuationmainTask 完成之前无法开始,因此通过此更改,您将等待mainTask continuation

    但请注意,如果从 UI 线程读取 IsFinished,您还需要从 UI 线程设置它。那,或者让它由 volatile 字段支持。

    【讨论】:

      【解决方案2】:

      曾经有一个名为“Busy Indicator”的第三方库。也许您可以使其仅在满足忙碌条件一段时间后才出现? (https://github.com/xceedsoftware/wpftoolkit/wiki/Xceed-Toolkit-Plus-for-WPF)。

      基本上它归结为 ViewModel 公开一个“忙碌”属性(或任何可以转换为表示“忙碌”的布尔值的属性)。并且视图会延迟(如果有)对更改做出反应。

      我不确定 XAML 本身是否可以做到这一点,因为您需要显示一个窗口。这里可能需要一些代码。你如何注册一个自定义的 ChangeNotification 处理程序来启动一个计时器,计时器重新检查“tick”事件中是否仍然满足条件?

      这里有一些代码,主要来自记忆:

      //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
        }
      }
      

      【讨论】:

        【解决方案3】:
                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 秒,它只会显示对话窗口。您也可以将所有这些包装在另一个任务中,然后调用者可以等待整个事情,无论是否显示对话框都没有区别。

        【讨论】:

        • 这个答案实际上没有帮助,因为它与我链接的答案非常相似。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-06-30
        • 2023-01-30
        • 2013-08-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多