异步延迟加载任务

时间:2012-04-07 23:39:12

标签: wpf properties lazy-loading task

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

理想情况下,我的财产看起来像这样:

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

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

有解决方案吗?

3 个答案:

答案 0 :(得分: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");
   }
}

答案 1 :(得分:0)

我所做的是检查realResult,如果为null,则返回“working”,然后调用BackGroundWorker。在BackGround的回调中分配realResult并调用NotifyPropertyChanged。属性上的异步没什么价值。我喜欢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";
    }
}

答案 2 :(得分:0)

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();