-
Notifications
You must be signed in to change notification settings - Fork 634
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
unblock main thread when fetching user votes #14793
unblock main thread when fetching user votes #14793
Conversation
- before fetching user votes could potentially slow down dynamo loading as it was being executed on the main thread - now using Task.Run to execute on a separate thread and unblock the loading process
@@ -285,7 +285,7 @@ internal PackageManagerClientViewModel(DynamoViewModel dynamoViewModel, PackageM | |||
|
|||
if (AuthenticationManager.LoginState.Equals(LoginState.LoggedIn)) | |||
{ | |||
this.Uservotes = this.Model.UserVotes(); | |||
Task.Run(() => this.Uservotes = this.Model.UserVotes()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is going to execute from other some other thread pool thread.
Does anything else write to UserVotes
? (another thread?) - it seems like it could be made readonly if not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I say keep it in a Task.Run, but move it closer to the greg client call.
Having it in the VIewModel is risky, because someone might add UI code (code that interacts with UI elements) in the UserVotes body
As @mjkkirschner mentioned, we need to make sure all data that is accessed by other thread is thread safe (in some way or another)
ALso we need to make sure we handle exceptions properly. If ExecuteAndDeserialize throws an exception, what happens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I got it. I moved the call to right before using the Uservotes
for the first time.
hey @pinzart90 what do you think about this note a the very start of the docs... For I/O-bound code, you await an operation that returns a Task or
I think in general refactoring all of our code to be async/await might not be worth the pain - though I guess the point of the above advice is that eventually if we continue this pattern of moving io bound code to threads, we'll be using threads just to unblock the main thread and taking on that overhead when we could have used async/await and avoided the thread overhead/ potential deadlocks,livelocks. |
I know we talked about this in the past a bit: My take on it is that we should not block use/block threadPool threads for something as simple as I/O code (file read/write, web access etc) when we already have an awaitable I/O operation to deal with. It might not be simple to refactor existing code to go from Task.Run to async/await. Example : all of our Greg code (that deals with packageManager requests) is not awaitable, so of course it is easier to stick it in another thread. |
…ocking-main-thread
@mjkkirschner @pinzart Just to confirm, do we want to hold off this change for 3.0.2 and refactor it in 3.1? |
UI Smoke TestsTest: success. 2 passed, 0 failed. |
UI Smoke TestsTest: success. 2 passed, 0 failed. |
@@ -482,6 +477,12 @@ public List<PackageManagerSearchElement> ListAll() | |||
{ | |||
CachedPackageList = new List<PackageManagerSearchElement>(); | |||
|
|||
// Attempt to load user votes prior to using it | |||
if (AuthenticationManager.LoginState.Equals(LoginState.LoggedIn) && Uservotes == null) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So you are basically caching UserVotes.
Will it be a problem that UserVotes will not refresh for subsequent calls to public List<PackageManagerSearchElement> ListAll()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a 15 minute lag until the database picks up the changes, as far as I know. The data is specific to the user. Which means, that they won't be able to see the subsequent change until the database picks it up, again this is as far as my knowledge goes on how fast the data will be registered.
With that, I think there is no problem that the UserVotes won't refresh in subsequent calls of ListAll().
// Attempt to load user votes prior to using it | ||
if (AuthenticationManager.LoginState.Equals(LoginState.LoggedIn) && Uservotes == null) | ||
{ | ||
Task.Run(() => this.Uservotes = this.Model.UserVotes()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also Task.Run() will run asynchronously on another thread. That means that the rest of the code will execute without waiting for the result.
Also won't Model.ListAll()
cause the same UI blocking issue ?
Ideally we should make all these methods async and cascade them down to a UI method that makes sense to be async. Something like DynamoViewModelRequestShowPackageManagerSearch should be async (and all the async-able calls downstream) and the UI should relate that it is doing something async (UI spinner etc). I realize this is too much for 3.0.x release.
I am no longer sure how we can make this work properly, since a part of the code needs the information synchronously (which means the UI thread needs to wait for that information)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OF course, just by moving the offending outside the constructor, you will no longer block the DynamoView initialization code (basically Dynamo load logic). We will now block the UI, but later on when the user actually requests PackageManager information.
Maybe we it is better than nothing.
We could also try to parallelize the calls something like
IEnumerable<PackageHeader> allPackages;
var task1 = Task.Run(() => Model.UserVotes());
var task2 = Task.Run(() => Model.ListAll());
Task.WhenAll(task1, task2).Wait();
Uservotes = task1.Result;
allPackages = task2.Result;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, looks like there are some unresolved discussions around REstSHarp's thread safety.
I think it is ok to call it from multiple threads, just not in parallel
So, runnig the code on the UI thread might be safer (There is no real value in using Task.Run except if you can do it async or parallelize it)
Hi @pinzart90 @mjkkirschner - would it be OK to merge this PR at this point? Or close it down in favor of a more centralized effort to align the correct behavior of all Greg async calls? Just to remind us, this PR addresses a lag/freeze on Dynamo loading on slow machines blocking the main thread detected by @mjkkirschner. It is probably a very minor change relative to the holistic conversation about thread safety. |
src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerClientViewModel.cs
Show resolved
Hide resolved
@@ -482,6 +477,8 @@ public List<PackageManagerSearchElement> ListAll() | |||
{ | |||
CachedPackageList = new List<PackageManagerSearchElement>(); | |||
|
|||
// Calls to Model.UserVotes and Model.ListAll might take a long time to run (so do not use them syncronously in the UI thread) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public List<PackageManagerSearchElement> ListAll()
is called from an async method upstream
It is not going to block the UI thread anymore.
We do need a clear way to mark these methods (that they are Task based or async) . This will be part of another task
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what async method calls ListAll()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dynamo/src/DynamoCoreWpf/ViewModels/PackageManager/PackageManagerSearchViewModel.cs
Line 1026 in d01e057
Task<IEnumerable<PackageManagerSearchElementViewModel>>.Factory.StartNew(RefreshAndSearch).ContinueWith((t) => |
Purpose
A small PR introducing an async loading of UserVotes to unload the main thread from potentially long operation (under slow or now internet?) and unblock Dynamo loading process.
Declarations
Check these if you believe they are true
*.resx
filesRelease Notes
Reviewers
@mjkkirschner
@aparajit-pratap
FYIs
@pinzart