UI freeze for 3-10 seconds while update data in UI thread I want to update data in UI thread without freeze.
Code:
Task t = Task.Factory.StartNew(() =>
{
// Get data from Server
GetData(true);
});
Inside Getdata()
//Converst JSON to DataSet Object:- "tempDataSet"
Task task = Task.Factory.StartNew(() =>
{
RetriveData(tempDataSet, firstTime);
}, CancellationToken.None, TaskCreationOptions.None, MainFrame.Current);
Inside RetriveData
DataTable response = tempDataSet.Tables["response"];
DataTable conversations = tempDataSet.Tables["convo"];
foreach (DataRow row in conversations.Rows) // UI Hangs in the method
{
UC_InboxControl control = new UC_InboxControl(row, uC_Inbox);
if (uC_Inbox.mnuUnreadChat.IsChecked == false)
{
inboxControlCollection.Add(control);
}
else
{
inboxUnreadOnlyControlCollection.Add(control);
}
}
What is the best approach to update UI in UI thread without hangs or freeze?
The GetData method should not access any UI elements. It should be executed on a background thread and return a list of objects that you want to display in the view. You could then use the ContinueWith method to populate the ObservableCollection with these objects back on the UI thread, e.g.:
Task t = Task.Factory.StartNew(() =>
{
return GetData(true); // <-- GetData should return a collection of objects
}).ContinueWith(task =>
{
//that you add to your ObservableCollection here:
foreach (var item in task.Result)
yourObservableCollection.Add(item);
},
System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
The same result can be achieved with async/await, which will restore the UI context after completing the task:
// await the task itself, after that do the UI stuff
var collection = await Task.Run(() =>
{
// directly call the retrieve data
return RetriveData(tempDataSet, firstTime);
});
// this code will resume on UI context
foreach (var item in collection)
{
var control = new UC_InboxControl(row, uC_Inbox);
if (!uC_Inbox.mnuUnreadChat.IsChecked)
{
inboxControlCollection.Add(control);
}
else
{
inboxUnreadOnlyControlCollection.Add(control);
}
}
As you can see, I call the RetriveData directly here. Also you can mark it as async too, so you can do:
public async Task<> GetData(...)
{
// some code ...
return await Task.Run(() =>
{
return RetriveData(tempDataSet, firstTime));
}
}
To achieve this you need to mark the method as async. If it is a event handler, you can use async void, in other case use async Task.
Related
I have a big time-consuming task and I try to implement asynchronous methods in order to prevent the application from blocking. My code looks like this:
CancellationTokenSource _cts;
async void asyncMethod()
{
// ..................
_cts = new CancellationTokenSource();
var progress = new Progress<double>(value => pbCalculationProgress.Value = value);
try
{
_cts.CancelAfter(25000);
int count = await awaitMethod(_cts.Token, progress);
}
catch (OperationCanceledException ex)
{
// .......
}
finally
{
_cts.Dispose();
}
// ..................
}
async Task<int> awaitMethod(CancellationToken ct, IProgress<double> progress)
{
var task = Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
sqlParser();
progress.Report(1);
return 0;
});
return await task;
}
void sqlParser()
{
string info = form1TxtBox.Text;
// ................
}
Also, the program throws an exception, because sqlParser() updates UI thread, when it retrieves the text from the form. The solution is to introduce Dispatcher method, which allows UI update. I keep the body of awaitMethod the same and simply put sqlParser() inside of the Dispatcher:
DispatcherOperation op = Dispatcher.BeginInvoke((Action)(() =>
{
sqlParser();
}));
Here happens something interesting: asyncMethod() even doesn't dare to call awaitMethod! However, if I put a breakpoint inside of sqlParser() and run debugger, then everything goes very smoothly.
Please, can somebody explain what I miss in my code? What kind of patch should i use to make Dispatcher work correctly? Or: how can I run my program without Dispatcher and without throwing UI-update exception?
The solution is to introduce Dispatcher method, which allows UI update.
That's never a good solution. Having background threads reaching directly into your UI is encouraging spaghetti code.
how can I run my program without Dispatcher and without throwing UI-update exception?
Think of your background thread code as its own separate component, completely separate from the UI. If your background code needs data from the UI, then have your UI code read it before the background code starts, and pass that data into the background code.
async void asyncMethod()
{
...
try
{
var data = myUiComponent.Text;
_cts.CancelAfter(25000);
int count = await awaitMethod(data, _cts.Token, progress);
}
...
}
async Task<int> awaitMethod(string data, CancellationToken ct, IProgress<double> progress)
{
var task = Task.Run(() =>
{
ct.ThrowIfCancellationRequested();
sqlParser(data);
progress.Report(1);
return 0;
});
return await task;
}
I have a task that performing some heavy work.
I need to path it's result to LogContent
Task<Tuple<SupportedComunicationFormats, List<Tuple<TimeSpan, string>>>>.Factory
.StartNew(() => DoWork(dlg.FileName))
.ContinueWith(obj => LogContent = obj.Result);
This is the property:
public Tuple<SupportedComunicationFormats, List<Tuple<TimeSpan, string>>> LogContent
{
get { return _logContent; }
private set
{
_logContent = value;
if (_logContent != null)
{
string entry = string.Format("Recognized {0} log file",_logContent.Item1);
_traceEntryQueue.AddEntry(Origin.Internal, entry);
}
}
}
Problem is that _traceEntryQueue is data bound to UI, and of cause I will have exception on code like this.
So, my question is how to make it work correctly?
Here is a good article: Parallel Programming: Task Schedulers and Synchronization Context.
Take a look at Task.ContinueWith() method.
Example:
var context = TaskScheduler.FromCurrentSynchronizationContext();
var task = new Task<TResult>(() =>
{
TResult r = ...;
return r;
});
task.ContinueWith(t =>
{
// Update UI (and UI-related data) here: success status.
// t.Result contains the result.
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, context);
task.ContinueWith(t =>
{
AggregateException aggregateException = t.Exception;
aggregateException.Handle(exception => true);
// Update UI (and UI-related data) here: failed status.
// t.Exception contains the occured exception.
},
CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, context);
task.Start();
Since .NET 4.5 supports async/await keywords (see also Task.Run vs Task.Factory.StartNew):
try
{
var result = await Task.Run(() => GetResult());
// Update UI: success.
// Use the result.
}
catch (Exception ex)
{
// Update UI: fail.
// Use the exception.
}
You need to run the ContinueWith -task on the UI thread. This can be accomplished using the TaskScheduler of the UI thread with the overloaded version of the ContinueWith -method, ie.
TaskScheduler scheduler = TaskScheduler.Current;
...ContinueWith(obj => LogContent = obj.Result), CancellationToken.None, TaskContinuationOptions.None, scheduler)
You can use the Dispatcher to invoke code on UI thread. Take a look at the article Working With The WPF Dispatcher
If you are using async/await, then here is some example code that shows how to schedule a task to run on the GUI thread. Place this code at the bottom of the stack of all of your async/await calls to avoid the WPF runtime throwing errors with code not executing on the GUI thread.
Works with WPF + MVVM, tested under VS 2013.
public async Task GridLayoutSetFromXmlAsync(string gridLayoutAsXml)
{
Task task = new Task(() => // Schedule some task here on the GUI thread );
task.RunSynchronously();
await task;
}
I have some problems with Task.Factory.StartNew and Task.WaitAll. The tasks do start as they should but it looks like it just ignores the Task.WaitAll, because after clicking my button (it's the event this code is in) the MessageBox already popsup.
List<Task> tasks = new List<Task>();
if (plugin.UseProxy)
{
foreach (var item in combo)
{
Task.Factory.StartNew(() =>
{
// Some code
}).ContinueWith((t) =>
{
tasks.Add(t);
pbProgress.Value++;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
else
{
foreach (var item in combo)
{
Task.Factory.StartNew(() =>
{
// Some code
}).ContinueWith((t) =>
{
tasks.Add(t);
pbProgress.Value++;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
Task.WaitAll(tasks.ToArray());
MessageBox.Show("Hello");
You only add the Tasks to your List when they finish. ContinueWith executes when the task is done. So Task.WaitAll is waiting an empty list of Tasks.
So you can do this:
Task task = Task.StartNew(() =>
{
// Some code
}).ContinueWith((t) =>
{
pbProgress.Value++;
}, TaskScheduler.FromCurrentSynchronizationContext());
tasks.Add(task);
The tasks list only get the items added to it once the thread started via the StartNew finishes. The problem you are having is you are hitting the Task.WaitAll(tasks.ToArray()); before the items are added to the collection. You need to add the items to the collection in the thread that is creating them, not in the continue with,
var newTask = Task.Factory.StartNew(() =>
{
// Some code
});
tasks.Add(newTask);
newTask.ContinueWith((t) =>
{
pbProgress.Value++;
}, TaskScheduler.FromCurrentSynchronizationContext());
However your code has other problems too. You never pass in a TaskSchedueller to the factory, if you don't you can easily accidentally start your thread on the UI thread. Also I assume this code is running on the UI thread, your Task.WaitAll will block the UI thread. This can lead to deadlocks if one of those StartNew threads ended up on a UI thread.
I have to create a method, that similar to ContinueWith(), but will execute continuation in main thread, after main Task.
How can I do that?
I could endlessly checking the state of Task in my method, and when it finishes start continuation, but I think it couldn`t work in such way:
Task<DayOfWeek> taskA = new Task<DayOfWeek>(() => DateTime.Today.DayOfWeek);
Task<string> continuation = taskA.OurMethod((antecedent) =>
{
return String.Format("Today is {0}.", antecedent.Result);
});
// Because we endlessly checking state of main Task
// Code below will never execute
taskA.Start();
So what I could do here?
Try passing around the "main" thread's Dispatcher. Example:
Task.Factory.StartNew(()=>
{
// blah
}
.ContinueWith(task=>
{
Application.Current.Dispatcher.BeginInvoke(new Action(()=>
{
// yay, on the UI thread...
}
}
Assuming that the "main" thread is UI thread. If it's not, then grab that thread's dispatcher after you make it. Use that dispatcher instead of Application.Current's (i.e. CurrentDispatcher).
You can create an ExtensionMethod for a process like this. Here is an example implementation
static class ExtensionMethods
{
public static Task ContinueOnUI(this Task task, Action continuation)
{
return task.ContinueWith((arg) =>
{
Dispatcher.CurrentDispatcher.Invoke(continuation);
});
}
}
Consume it like this.
Task run = new Task(() =>
{
Debug.WriteLine("Testing");
});
run.ContinueOnUI(() =>
{
Notify += "\nExecuted On UI"; // Notify is bound on a UI control
});
run.Start();
I have a task that performing some heavy work.
I need to path it's result to LogContent
Task<Tuple<SupportedComunicationFormats, List<Tuple<TimeSpan, string>>>>.Factory
.StartNew(() => DoWork(dlg.FileName))
.ContinueWith(obj => LogContent = obj.Result);
This is the property:
public Tuple<SupportedComunicationFormats, List<Tuple<TimeSpan, string>>> LogContent
{
get { return _logContent; }
private set
{
_logContent = value;
if (_logContent != null)
{
string entry = string.Format("Recognized {0} log file",_logContent.Item1);
_traceEntryQueue.AddEntry(Origin.Internal, entry);
}
}
}
Problem is that _traceEntryQueue is data bound to UI, and of cause I will have exception on code like this.
So, my question is how to make it work correctly?
Here is a good article: Parallel Programming: Task Schedulers and Synchronization Context.
Take a look at Task.ContinueWith() method.
Example:
var context = TaskScheduler.FromCurrentSynchronizationContext();
var task = new Task<TResult>(() =>
{
TResult r = ...;
return r;
});
task.ContinueWith(t =>
{
// Update UI (and UI-related data) here: success status.
// t.Result contains the result.
},
CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, context);
task.ContinueWith(t =>
{
AggregateException aggregateException = t.Exception;
aggregateException.Handle(exception => true);
// Update UI (and UI-related data) here: failed status.
// t.Exception contains the occured exception.
},
CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, context);
task.Start();
Since .NET 4.5 supports async/await keywords (see also Task.Run vs Task.Factory.StartNew):
try
{
var result = await Task.Run(() => GetResult());
// Update UI: success.
// Use the result.
}
catch (Exception ex)
{
// Update UI: fail.
// Use the exception.
}
You need to run the ContinueWith -task on the UI thread. This can be accomplished using the TaskScheduler of the UI thread with the overloaded version of the ContinueWith -method, ie.
TaskScheduler scheduler = TaskScheduler.Current;
...ContinueWith(obj => LogContent = obj.Result), CancellationToken.None, TaskContinuationOptions.None, scheduler)
You can use the Dispatcher to invoke code on UI thread. Take a look at the article Working With The WPF Dispatcher
If you are using async/await, then here is some example code that shows how to schedule a task to run on the GUI thread. Place this code at the bottom of the stack of all of your async/await calls to avoid the WPF runtime throwing errors with code not executing on the GUI thread.
Works with WPF + MVVM, tested under VS 2013.
public async Task GridLayoutSetFromXmlAsync(string gridLayoutAsXml)
{
Task task = new Task(() => // Schedule some task here on the GUI thread );
task.RunSynchronously();
await task;
}