Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

The 3 VS Threading Rules

547 vues

Publié le

Walks through the 3 threading rules documented at https://aka.ms/vsthreading to assist in writing safe threading and async code in Visual Studio.

Publié dans : Logiciels
  • Want to earn $4000/m? Of course you do. Learn how when you join today! ▲▲▲ https://tinyurl.com/y4urott2
       Répondre 
    Voulez-vous vraiment ?  Oui  Non
    Votre message apparaîtra ici
  • ♣♣ 10 Easy Ways to Improve Your Performance in Bed... ◆◆◆ https://tinyurl.com/rockhardxx
       Répondre 
    Voulez-vous vraiment ?  Oui  Non
    Votre message apparaîtra ici
  • Soyez le premier à aimer ceci

The 3 VS Threading Rules

  1. 1. The 3 VS Threading Rules WATCH THE VIDEO OF THE ORIGINAL PRESENTATION
  2. 2. The history of RPC calls, p. 1  RPC = Remote Procedure Call  It blocks the calling thread, sends a high priority message to the UI thread and only unblocks the calling thread after the UI thread processes the message.  Allows the caller to remain unaware of threading requirements of the COM object it is invoking.  Is often used in scenarios where you have code that kicks off background thread work and blocks the UI thread on its completion, since RPC can penetrate the blocking wait so that the background thread work can use the UI thread as part of its work.
  3. 3. The history of RPC calls, p. 2  Implicitly used whenever native or managed code calls a COM object implemented in native code (roughly).  Also explicitly accessible in managed code via ThreadHelper.Generic.Invoke.
  4. 4. The history of RPC calls, p. 3  Let’s redefine RPC to mean “Re-entrant problematic call”  RPC “re-enters” the UI thread and interrupts whatever it is doing, even irrelevant work.  Even highly filtered message pumps process RPC messages.  Hangs occur when the message pump is disabled.  Hurts responsiveness because RPC messages are processed before user input.  Can quickly exhaust the threadpool while waiting for the UI thread, leading to very long (80+ seconds) hangs for the user.
  5. 5. The history of RPC calls, p. 4  Managed code is littered with hidden message pumps:  C# lock keyword  Literally everything that may result in a synchronously blocking wait because SynchronizationContext.Wait can pump.  Native code must explicitly request pumping (via CoWait or other Co- calls), but these too are hidden inside unsuspecting APIs:  IVsOutputWindowPane::OutputString (some implementations)  Any time a message pump runs, RPC can get unrelated work in, which can hang, corrupt data, or slow down a scenario.
  6. 6. Question to ponder  Q: What's worse? Code that hangs some of the time or all of the time?  It is preferable to consistently fail than to inconsistently succeed. Consistent failures are much more likely to be found before shipping, and are easier to analyze, fix, and verify.  The 3 threading rules are designed around this high level behavioral preference.
  7. 7. Question to ponder  Q: What priority should you use to get to the UI thread?  Old Answer: Traditionally the greatest input in making this decision has been based on finding the balance between re-entrancy and deadlocks rather than what will provide the best user experience. The priority is set in the code that schedules the individual task.  Preferred answer: This should be determined by the top-level scenario, and the majority of the working code should be oblivious to it. The priority is ‘inherited’ by the code that schedules the individual task.
  8. 8. The 3 VS threading rules 1. Use JoinableTaskFactory.SwitchToMainThreadAsync() 2. Use JoinableTaskFactory.Run() 3. Use JoinableTaskFactory.RunAsync()  Flexible priority to get to the UI thread  Never deadlocks due to WPF turning off the message pump  Deadlocks somewhat more reliably than older methods when you break the rules (this is a good thing!)
  9. 9. The 3 VS threading rules: Rule #1 Use JoinableTaskFactory.SwitchToMainThreadAsync()  If a method is not free threaded, it must either:  Be async and switch using SwitchToMainThreadAsync() prior to calling STA objects, OR  Be sync and call ThreadHelper.ThrowIfNotOnUIThread();  This is the only way you should ever need. Throw all other approaches out.  Never use synchronous blocking methods:  ThreadHelper.Generic.Invoke  SynchronizationContext.Send  RPC (implicit marshaling)
  10. 10. The 3 VS threading rules: Rule #1 async Task DoSomethingAsync() { // We’re on the caller’s thread, whatever that is. await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); // Now we’re on the UI thread! It’s safe to call STA objects. var solution = Package.GetService(typeof(SVsSolution)) as IVsSolution; } void DoSomething() { ThreadHelper.ThrowIfNotOnUIThread(); var solution = Package.GetService(typeof(SVsSolution)) as IVsSolution; }
  11. 11. The 3 VS threading rules: Rule #1  Q: Why is it better to throw an exception from a synchronous method when invoked from the wrong thread than to try to make it work?  A: It’s better to throw consistently than to hang inconsistently because bugs will be more likely caught before being checked in or shipped.  Be cautious to not break backward compatibility. When it used to work when called from a background thread, use Rule #2.  Q: What if you’re already on the UI thread?  A: SwitchToMainThreadAsync is a very cheap no-op call. Use it liberally.
  12. 12. The 3 VS threading rules: Rule #1  Remember the implicit UI thread dependencies:  In C#, casting a COM object is an STA call to IUnknown::QueryInterface  Invoking an interface member, implemented by a native COM object  Consider avoiding other async scheduling:  SynchronizationContext.Post  ThreadHelper.Generic.BeginInvoke
  13. 13. What about getting off the UI thread?  Explicitly switching off the UI thread is great for:  Disk or network access  CPU intensive operation  Switch away from the UI thread any way you please:  await TaskScheduler.Default;  await Task.Run(…);  Caution  Get off the UI thread only within async methods that you know only invokes code that follow the 3 threading rules.  Avoid Task.Factory.StartNew or Task.Factory.ContinueWith unless you specify TaskScheduler.Default explicitly.
  14. 14. The 3 VS threading rules: Rule #2 Use JoinableTaskFactory.Run  When a synchronous method needs to call async methods:  Preferably make your sync method async.  When that cannot be done, use JoinableTaskFactory.Run.  Never use these on an incomplete Task:  Task.Result  Task.Wait()  Bad:  var result = GetValueAsync().Result;  DoSomethingAsync().Wait();
  15. 15. The 3 VS threading rules: Rule #2 async Task DoSomethingAsync() { // We’re on the caller’s thread, whatever that is. await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); // Now we’re on the UI thread! It’s safe to call STA objects. var solution = Package.GetService(typeof(SVsSolution)) as IVsSolution; } void DoSomething() { ThreadHelper.JoinableTaskFactory.Run(async delegate { await DoSomethingAsync(); }); }
  16. 16. The 3 VS threading rules: Rule #2  Notice how we only have to implement the method once (asynchronously), and we have our synchronous method simply call to the async one using rule #2.  This allows async callers to call the async version of our method, avoiding threadpool exhaustion.  Our code is more maintainable because there is only one copy of the logic.
  17. 17. The 3 VS threading rules: Rule #3 Use JoinableTaskFactory.RunAsync (and Join)  Use when an async operation might later block the UI thread.  When it’s time to block the calling thread, use JoinableTask.Join()  Or even better: await joinableTask;  Never use these on an incomplete Task: (it’s worth repeating)  Task.Result  Task.Wait()  Bad:  var result = GetValueAsync().Result;  DoSomethingAsync().Wait();
  18. 18. The 3 VS threading rules: Rule #3 // Simple case (but not that common, really) private JoinableTask asyncWork; void StartWorkAsync() { this.asyncWork = ThreadHelper.JoinableTaskFactory.RunAsync( async delegate { await SomethingAsync(); }); } void FinishNow() { this.asyncWork.Join(); // blocks till complete, without deadlocking }
  19. 19. The 3 VS threading rules: Rule #3 // More common case private JoinableTask asyncWork; void StartWorkAsync() { this.asyncWork = ThreadHelper.JoinableTaskFactory.RunAsync( async delegate { await SomethingAsync(); }); } void FinishNow() { ThreadHelper.JoinableTaskFactory.Run(async delegate { await this.asyncWork; }); }
  20. 20. The 3 VS threading rules: Rule #3  Avoid exposing JoinableTask as the return type from your public API.  Simply return Task. It’s the caller’s responsibility to wrap with RunAsync if necessary.  When you have your own scheduler/queue, more advanced techniques are required!
  21. 21. What about IVsTask?  IVsTask is still required if you need to implement an async COM interface.  Don’t bother using VsTaskLibraryHelper directly to construct IVsTask. That’s too verbose.  But you can’t return IVsTask from a C# async method. So how do you do it?
  22. 22. How to return IVsTask from C# async public IVsTask DoAsync() { return ThreadHelper.JoinableTaskFactory.RunAsyncAsVsTask( VsTaskRunContext.UIThreadNormalPriority, // (or lower UI thread priorities) async cancellationToken => { await SomethingAsync(cancellationToken); await SomethingElseAsync(); }); }
  23. 23. IVsTask tips  You can await IVsTask just fine and JoinableTaskFactory will do the right thing.  Remember that JoinableTaskFactory.RunAsync(VsTaskRunContext) does not automatically switch you to the UI thread!
  24. 24. Tip #1: Conditional switching  If you depend on the UI thread for some code paths, verify it is available for all of them. This makes failure more consistent and likely to be spotted. // AVOID THIS PATTERN async Task DoSomethingAsync() { if (someSwitch) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); // Use UI thread } // WARNING: This code may run on either context }
  25. 25. Tip #1: Conditional switching  If you depend on the UI thread for some code paths, verify it is available for all of them. This makes failure more consistent and likely to be spotted. // PREFERRED pattern async Task DoSomethingAsync() { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); if (someSwitch) { // conditional logic } // This code is consistently on the UI thread }
  26. 26. Tip #2: Async constructors  Constructors cannot be async. But static factory methods can! class MyClass { private MyClass(string someValue) { … } internal static Task<MyClass> CreateAsync() { string value = await DoSomethingAsync(); return new MyClass(value); } }
  27. 27. Tip #3: Avoid nesting JTF.Run  Use JTF.Run around the entire body of your public, sync methods.  Avoid using JTF.Run with a delegate that calls another method that itself uses JTF.Run. These can be nested, but the complexity and overhead of multiple calls is nice to avoid and helps you be async wherever possible. int DoSomething () { // BAD EXAMPLE SomeWork(); int value = JTF.Run(async () => { return await GetValueAsync(); }); return value; }
  28. 28. Tip #3: Avoid nesting JTF.Run  Use JTF.Run around the entire body of your public, sync methods.  Avoid using JTF.Run with a delegate that calls another method that itself uses JTF.Run. These can be nested, but the complexity and overhead of multiple calls is nice to avoid and helps you be async wherever possible. int DoSomething() { // BETTER EXAMPLE return JTF.Run(async delegate { SomeWork(); int value = await GetValueAsync() return value; }); }
  29. 29. More Tips  Mock up the UI thread in unit tests to verify you won't deadlock.  JoinableTaskFactory can be used in your unit tests  Never construct a JoinableTaskContext yourself except in unit tests.  Use the one on ThreadHelper when in devenv.exe.  Don't exhaust the threadpool! (it's a violation of rule #1 anyway)  Remember: MEF parts can be instantiated on any thread
  30. 30. Appendix  More about the 3 threading rules  Recipes for common VS scenarios  Async hang debugging tips

×