Generally it is dangerous to use Task.Factory.StartNew(...)
when working with code related to the UI thread, because the first time it will examine the TaskScheduler
, find that there isn't one, and it will use a thread pool TaskScheduler
. When the thread is done computing using a function called, say, Compute(3)
, it returns but here it marshals back to the UI thread, then will update the UI thread with the results so far.
The second and third times it calls Compute, since the SynchronizationContext has been marshaled back to the UI, it will then run on a UI thread, thus blocking your UI.
private void Form1_Load(object sender, EventArgs e)
{
Task.Factory.StartNew(A);
}
private static void A() { }
private void Form1_Load(object sender, EventArgs e)
{
Compute(3);
}
private void Compute(int counter)
{
// If we're done computing, just return.
if (counter == 0)
return;
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => A(counter))
.ContinueWith(t =>
{
Text = t.Result.ToString(); // Update UI with results.
// Continue working.
Compute(counter - 1);
}, ui);
}
private int A(int value)
{
return value; // CPU-intensive work.
}
To avoid this potential problem, instead use Task.Run(() => A());
For more details, please see Stephen Cleary's article at https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html and/or What is the difference between Task.Run() and Task.Factory.StartNew()
Here is a way that I've found to load data for a datagrid in the background while not blocking your UI.
First, create a lock object, and enable collection synchronization, then actually load the data on a background thread using Task.Run():
private readonly object _myDataLock = new object();
private FastObservableCollection<My20FieldsDataRecord> MyList = new FastObservableCollection<My20FieldsDataRecord>();
private CollectionViewSource MyListCollectionView = new CollectionViewSource();
public MyViewModelConstructor() : base()
{
// Other ctor code
// ...
// assign the data source of the collection views
MyListCollectionView.Source = MyList;
// Setup synchronization
BindingOperations.EnableCollectionSynchronization(MyList, _myDataLock);
}
private async void LoadMyList()
{
// load the list
await Task.Run(async () =>
{
MyList.ReplaceAll(await MyRepository.LoadMyList());
}
);
}
Then in your repository you could write:
public virtual async Task<IList<My20FieldsDataRecord>> LoadMyList()
{
var results = await this.DataContext.TwentyFieldDataRecords
.OrderByDescending(x => x.MyDate).ToListAsync().ConfigureAwait(false);
return results;
}
Then you could bind in your associated view like this:
<controls:DataGrid Name="MyListDataGrid" Grid.Row="1"
....
ItemsSource="{Binding MyListCollectionView.View}"
... >
For details, please see: