2

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?

3 Answers 3

1

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");
   }
}
Sign up to request clarification or add additional context in comments.

5 Comments

But where does Dispatcher come from?
It comes from DispatcherObject.Dispatcher Property. The most classes are inherited from DispatcherObject.
You could inherit your class with MyProperty from DependencyObject Class or use Application.Current.Dispatcher.
I can't inherit from DispatcherObject because it's a business object.
Then use Application.Current.Dispatcher.
0

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

Comments

0

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.