【问题标题】:async lazy loading with tasks异步延迟加载任务
【发布时间】:2012-04-07 23:39:11
【问题描述】:

我正在对可能很慢的属性使用数据绑定。但是,我不想冻结 UI。相反,我想使用新的任务库。

理想情况下,我的财产应该是这样的:

public string MyProperty
{
   get
   {
      if (_cache != null)
         return _cache;
      var result = await SomeSlowFunction();
      return result;
   }
}

但是,这现在确实有效,因为属性永远不会异步。

有解决办法吗?

【问题讨论】:

    标签: wpf properties lazy-loading task


    【解决方案1】:

    我假设你已经实现了INotifyPropertyChanged。那么也许这样的事情可以完成这项工作:

    private string _myProperty;
    public string MyProperty
    {
       get
       {
          if (_myProperty != null)
             return _myProperty;
          MyProperty = Application.Current.Dispatcher.BeginInvoke((Action)(() => SomeSlowFunction()));
          return string.Empty;
       }
       set
       {
          if (_myProperty == value) return;
          _myProperty = value;
          RaiseNotifyPropertyChanged("MyProperty");
       }
    }
    

    【讨论】:

    • 但是 Dispatcher 是从哪里来的呢?
    • 来自DispatcherObject.Dispatcher Property。大多数类都继承自DispatcherObject
    • 您可以使用 MyProperty 从 DependencyObject Class 或使用 Application.Current.Dispatcher 继承您的类。
    • 我不能从 DispatcherObject 继承,因为它是一个业务对象。
    • 然后使用Application.Current.Dispatcher
    【解决方案2】:

    Blam 的想法是正确的:您确实需要某种“进行中”的返回值,因为 UI 数据绑定立即需要一个值。但是,您不需要BackgroundWorker

    如果您将null 用于“进行中”的值,那么您可以执行以下操作:

    // If _cache is not null, then we already have a calculated property value.
    private string _cache;
    
    // If _cacheTask is not null, then the property is being calculated.
    private Task<string> _cacheTask;
    
    public string MyProperty
    {
      get
      {
        if (_cache != null)
          return _cache;
        if (_cacheTask != null)
          return null; // (in progress)
        StartSomeSlowFunction();
        // Note: _cacheTask is not null at this point.
        return null; // (in progress)
      }
    
      set
      {
        if (value == _cache)
          return;
        _cache = value;
        var propertyChanged = PropertyChanged;
        if (propertyChanged != null)
          propertyChanged(new PropertyChangedEventArgs("MyProperty"));
      }
    }
    
    private async Task StartSomeSlowFunction()
    {
      // Immediately start SomeSlowFunction and set _cacheTask.
      _cacheTask = SomeSlowFunction();
    
      // Asynchronously wait for SomeSlowFunction to complete,
      //  and set the property with the result.
      MyProperty = await _cacheTask;
    
      // Let the task be GCed; this also allows the property to be
      //  recalculated if it is set to null first.
      _cacheTask = null;
    }
    
    private async Task<string> SomeSlowFunction();
    

    【讨论】:

      【解决方案3】:

      我要做的是检查 realResult,如果它为 null,则返回“工作”,然后调用 BackGroundWorker。在来自 BackGround 的回调中分配 realResult 并调用 NotifyPropertyChanged。 async on property 没有什么价值。我喜欢 BackGroundWorker 的结构以及取消和报告进度的能力。

          private System.ComponentModel.BackgroundWorker backgroundWorker1;
          private string textBackGround;
      
      
          public event PropertyChangedEventHandler PropertyChanged;
          protected void NotifyPropertyChanged(String info)
          {
              if (PropertyChanged != null)
              {
                  PropertyChanged(this, new PropertyChangedEventArgs(info));
              }
          }
      
          public MainWindow()
          {
              backgroundWorker1 = new BackgroundWorker();
              backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
              backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
      
              InitializeComponent();
          }
      
          public string TextBackGround
          {
              get
              {
                  if (!string.IsNullOrEmpty(textBackGround)) return textBackGround;
                  backgroundWorker1.RunWorkerAsync();
                  return "working";             
              }
          }
      
          // This event handler is where the actual,
          // potentially time-consuming work is done.
          private void backgroundWorker1_DoWork(object sender,
              DoWorkEventArgs e)
          {
              // Get the BackgroundWorker that raised this event.
              BackgroundWorker worker = sender as BackgroundWorker;
      
              // Assign the result of the computation
              // to the Result property of the DoWorkEventArgs
              // object. This is will be available to the 
              // RunWorkerCompleted eventhandler.
              e.Result = ComputeFibonacci(worker, e);
          }
      
          // This event handler deals with the results of the
          // background operation.
          private void backgroundWorker1_RunWorkerCompleted(
              object sender, RunWorkerCompletedEventArgs e)
          {
              // First, handle the case where an exception was thrown.
              if (e.Error != null)
              {
                  MessageBox.Show(e.Error.Message);
              }
              else if (e.Cancelled)
              {
                  // Next, handle the case where the user canceled 
                  // the operation.
                  // Note that due to a race condition in 
                  // the DoWork event handler, the Cancelled
                  // flag may not have been set, even though
                  // CancelAsync was called.
                  textBackGround = "Cancelled";
                  NotifyPropertyChanged("TextBackGround");
              }
              else
              {
                  // Finally, handle the case where the operation 
                  // succeeded.
                  textBackGround = e.Result.ToString();
                  NotifyPropertyChanged("TextBackGround");
              }
          }
      
      
          // This is the method that does the actual work. For this
          // example, it computes a Fibonacci number and
          // reports progress as it does its work.
          string ComputeFibonacci(BackgroundWorker worker, DoWorkEventArgs e)
          {
      
              // Abort the operation if the user has canceled.
              // Note that a call to CancelAsync may have set 
              // CancellationPending to true just after the
              // last invocation of this method exits, so this 
              // code will not have the opportunity to set the 
              // DoWorkEventArgs.Cancel flag to true. This means
              // that RunWorkerCompletedEventArgs.Cancelled will
              // not be set to true in your RunWorkerCompleted
              // event handler. This is a race condition.
      
              if (worker.CancellationPending)
              {
                  e.Cancel = true;
                  return "cancelled";
              }
              Thread.Sleep(1000);
              if (worker.CancellationPending)
              {
                  e.Cancel = true;
                  return "cancelled"; 
              }
              return "complete";
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-08-01
        • 1970-01-01
        • 1970-01-01
        • 2011-10-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多