Reputation: 9903
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
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
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
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