user380719
user380719

Reputation: 9903

async lazy loading with tasks

I am using data binding against a property that may be slow. However, I don't want to freeze the UI. Instead, I want to use the new task library.

Ideally, my property would look like this:

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

However, this does now work because properties are never async.

Is there a solution?

Upvotes: 2

Views: 1350

Answers (3)

LPL
LPL

Reputation: 17063

I assume you have implemented INotifyPropertyChanged. Then maybe something like this can do the job:

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");
   }
}

Upvotes: 1

paparazzo
paparazzo

Reputation: 45096

What I do is check for realResult and if it is null return "working" then call BackGroundWorker. On the callback from BackGround assign realResult and call NotifyPropertyChanged. async on property is of little value. I like the structure of BackGroundWorker and the ability to cancel and report progress.

    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";
    }
}

Upvotes: 0

Stephen Cleary
Stephen Cleary

Reputation: 456437

Blam has the right idea: you do need some kind of "in progress" return value because the UI data binding needs a value immediately. However, you don't need BackgroundWorker.

If you use null for an "in progress" value, then you can do something like this:

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

Upvotes: 0

Related Questions