Reputation: 980
I have this function that bruteforces GUIDs to calculate IDs (GUIDs are hash calculations of the ID, so reversing the procedure can't be done by nothing else but bruteforcing through 2^32-1 (yes, that's right) possible IDs.
I used await Task.Delay(1);
to "refresh" the UI thread so everything looks natural, however this slows down the process too much, but if I were to use await Task.Delay(0);
the whole app would freeze and i wouldn't know the current progress of the search (shown as the 2 IProgress
s for the progress bar.
async public Task<Dictionary<string, string>> GetSteamIds(Dictionary<string,string> GUIDs, IProgress<UInt32> progress, IProgress<double> progress2)
{
List<string> Progress = new List<string>();
foreach(string GUID in GUIDs.Keys)
{
Progress.Add(GUID);
}
for (;Iteration <= UInt32.MaxValue;Iteration++)
{
await Task.Delay(0);
string guid = CalculateGUID(MinAcc+Iteration);
if(GUIDs.ContainsKey(guid))
{
GUIDs[guid] = Convert.ToString((Int64)(MinAcc + Iteration));
}
progress.Report(Iteration);
progress2.Report((1.0 * Iteration / UInt32.MaxValue * 100));
if(Progress.Count==0)
{
if (progress2 != null)
{
progress2.Report(100);
}
break;
}
}
return GUIDs;
}
I do not know how to mitigate this and even though my whole method is async, and the call is async, it still sits on the "UI thread" and I don't know how to work around this.
Naturally, I would appreciate a good answer that works, but I would also appreciate some elaboration to how this all works. I've read quite a bit of articles but none really explained it to me in a way for me to understand everything.
Upvotes: 0
Views: 2282
Reputation: 203802
Adding the async
keyword to a method doesn't actually make the method asynchronous, it just means that you're allowed to use the await
keyword inside of the method. You've written an entirely synchronous CPU bound method and just tagged the method as async
, making it still just a synchronous CPU bound method, and calling a synchronous CPU bound method from the UI thread is going to block the UI thread, preventing other UI actions from taking place.
Adding in an await Task.Delay(1)
is making your method still do all of its long running CPU bound work in the UI thread, it's just stopping every once in a while to move to the end of the line, allowing other operations to run. Not only is this slowing down your work, it also means that the UI thread isn't able to perform other UI operations the vast majority of the time as this process is using up so much of its time.
The solution is to simply not do the long running CPU bound work in the UI thread at all, and to offload that work to another thread.
Since this method of your isn't actually asynchronous, you should remove the async
keyword from the method (and change the return type accordingly). When you call this method, if you happen to need to do so from the UI thread, simply wrap it in a call to Task.Run
which will offload it to a thread pool thread. If that UI operation need to do work after it has completed with the result, then that UI operation can await
the result of Task.Run
, and that method should be marked as async
, because it really is doing its work asynchronously.
Upvotes: 4
Reputation: 40858
Don't run the task on the UI thread.
You could call it like this:
var myTask = Task.Run(() => GetSteamIds(...));
And keep a reference to myTask
somewhere so you can test myTask.IsCompleted
to see when it's done.
Or, you can use ContinueWith to automatically run some other code when the Task completes. For example:
var myTask = Task.Run(() => GetSteamIds(...)).ContinueWith(() => {
//do something here now that the task is done
}, TaskScheduler.FromCurrentSynchronizationContext());
The TaskScheduler.FromCurrentSynchronizationContext()
is to make sure that the continuation code runs on the UI thread (since you will likely want to update the UI with the results).
With this usage, there is no need for your method to be asynchronous at all (it does not need to return Task<>).
Or use BackgroundWorker, as suggested in the comments.
Upvotes: 3
Reputation: 456322
Your GetSteamIds
method is entirely synchronous and CPU-bound, so it should have a synchronous signature:
public Dictionary<string, string> GetSteamIds(Dictionary<string,string> GUIDs, IProgress<UInt32> progress, IProgress<double> progress2);
Now, when you want to invoke this (synchronous, CPU-bound) method from the UI thread, use await Task.Run
to run it on a threadpool thread and (asynchronously) get the result of the method:
var results = await Task.Run(() => GetSteamIds(guids, progress, progress2));
If you're using Progress<T>
, then it will take care of handling progress updates on the UI thread for you; there's no need for the awkward Dispatcher
or Control.Invoke
.
On a side note, with a very tight CPU-bound loop like this, you may find your progress reports themselves will slow down your UI and degrade your user experience. In that case, consider using my ObservableProgress, an IProgress<T>
implementation that throttles progress updates.
Upvotes: 3
Reputation: 173
when you call await Task.Delay(1)
it will actually give back access to the UI thread because probably the problem is not in the method you are calling, it is in the call of the method.
So you should remove the Task.Delay() in the method and show us the call of the method so we can help you further
Upvotes: -1