You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/extensibility/how-to-consume-brokered-service.md
+34-35Lines changed: 34 additions & 35 deletions
Original file line number
Diff line number
Diff line change
@@ -21,9 +21,9 @@ With all code in this document, activating C#'s [nullable reference types](/dotn
21
21
## Retrieving an IServiceBroker
22
22
23
23
To acquire a brokered service, you must first have an instance of <xref:Microsoft.ServiceHub.Framework.IServiceBroker>.
24
-
When your code is running in the context of MEF or a VS Package, you typically want the global service broker.
24
+
When your code is running in the context of MEF (Managed Extensibility Framework) or a VSPackage, you typically want the global service broker.
25
25
26
-
Brokered services themselves should use the <xref:Microsoft.ServiceHub.Framework.IServiceBroker> that they are assigned when their [service factory](xref:Microsoft.VisualStudio.Shell.ServiceBroker.BrokeredServiceFactory) is invoked.
26
+
Brokered services themselves should use the <xref:Microsoft.ServiceHub.Framework.IServiceBroker> that they're assigned when their [service factory](xref:Microsoft.VisualStudio.Shell.ServiceBroker.BrokeredServiceFactory) is invoked.
Starting with Visual Studio 2022, code running in a MEF activated extension may import the global service broker:
39
+
Starting with Visual Studio 2022, code running in a MEF activated extension can import the global service broker:
40
40
41
41
```cs
42
42
[Import(typeof(SVsFullAccessServiceBroker))]
@@ -51,8 +51,8 @@ We recommend that each client/class in your extension acquire its own service br
51
51
This pattern also encourages secure coding patterns where a brokered service should *not* be using the global service broker.
52
52
53
53
> [!IMPORTANT]
54
-
> Implementations of <xref:Microsoft.ServiceHub.Framework.IServiceBroker> do not typically implement <xref:System.IDisposable>, but these objects cannot be collected while <xref:Microsoft.ServiceHub.Framework.IServiceBroker.AvailabilityChanged> handlers exist.
55
-
Be sure to balance add/remove of event handlers, especially when the code may discard the service broker during the lifetime of the process.
54
+
> Implementations of <xref:Microsoft.ServiceHub.Framework.IServiceBroker> do not typically implement <xref:System.IDisposable>, but these objects can't be collected while <xref:Microsoft.ServiceHub.Framework.IServiceBroker.AvailabilityChanged> handlers exist.
55
+
Be sure to balance add/remove of event handlers, especially when the code might discard the service broker during the lifetime of the process.
56
56
57
57
### Context specific service brokers
58
58
@@ -80,28 +80,28 @@ using (myService as IDisposable)
80
80
}
81
81
```
82
82
83
-
As with all brokered service requests, the above code will activate a new instance of a brokered service.
84
-
After using the service, the code above disposes of the proxy as execution exits the `using` block.
83
+
As with all brokered service requests, the preceding code activates a new instance of a brokered service.
84
+
After using the service, the preceding code disposes of the proxy as execution exits the `using` block.
85
85
86
86
> [!IMPORTANT]
87
-
> Every proxy retrieved must be disposed of, *even if the service interface does not derive from <xref:System.IDisposable>*.
87
+
> Every proxy retrieved must be disposed of, *even if the service interface doesn't derive from <xref:System.IDisposable>*.
88
88
> Disposal is important because the proxy often has I/O resources backing it that prevent it from being garbage collected.
89
89
> Disposal terminates the I/O, allowing the proxy to be garbage collected.
90
90
> Use a conditional cast to <xref:System.IDisposable> for disposal and be prepared for the cast to fail to avoid an exception for `null` proxies or proxies that do not actually implement <xref:System.IDisposable>.
91
91
92
92
Be sure to install the latest [Microsoft.ServiceHub.Analyzers](https://www.nuget.org/packages/Microsoft.ServiceHub.Analyzers) NuGet package and keep the ISBxxxx analyzer rules enabled to help prevent such leaks.
93
93
94
-
Disposal of the proxy will result in disposal of the brokered service that was dedicated to that client.
94
+
Disposal of the proxy results in disposal of the brokered service that was dedicated to that client.
95
95
96
-
If your code requires a brokered service and cannot complete its work when the service is not available, you may want to display an error dialog to the user if the code owns the user experience rather than throw an exception.
96
+
If your code requires a brokered service and can't complete its work when the service isn't available, you might display an error dialog to the user if the code owns the user experience rather than throw an exception.
97
97
98
98
### Client RPC targets
99
99
100
-
Some brokered services accept or require a client RPC target for "callbacks".
100
+
Some brokered services accept or require a client RPC (Remote Procedure Call) target for "callbacks."
101
101
Such an option or requirement should be in the documentation of that particular brokered service.
102
102
For Visual Studio brokered services this information should be included in the IntelliSense documentation on the descriptor.
103
103
104
-
In such case, a client may provide one using <xref:Microsoft.ServiceHub.Framework.ServiceActivationOptions.ClientRpcTarget?displayProperty=nameWithType> like this:
104
+
In such case, a client can provide one using <xref:Microsoft.ServiceHub.Framework.ServiceActivationOptions.ClientRpcTarget?displayProperty=nameWithType> like this:
@@ -120,7 +120,7 @@ This proxy forwards calls and events each direction, with some important differe
120
120
121
121
### Observer pattern
122
122
123
-
If the service contract takes parameters of type <xref:System.IObserver%601>, you may learn more about how to construct such a type at [How to implement an observer](/dotnet/standard/events/how-to-implement-an-observer).
123
+
If the service contract takes parameters of type <xref:System.IObserver%601>, you can learn more about how to construct such a type at [How to implement an observer](/dotnet/standard/events/how-to-implement-an-observer).
124
124
125
125
An <xref:System.Threading.Tasks.Dataflow.ActionBlock%601> can be adapted to implement <xref:System.IObserver%601> with the <xref:System.Threading.Tasks.Dataflow.DataflowBlock.AsObserver%2A> extension method.
126
126
The [System.Reactive.Observer](/previous-versions/dotnet/reactive-extensions/hh229899(v=vs.103)) class from the Reactive framework is another alternative to implementing the interface yourself.
@@ -130,15 +130,15 @@ The [System.Reactive.Observer](/previous-versions/dotnet/reactive-extensions/hh2
130
130
- Expect <xref:StreamJsonRpc.RemoteInvocationException> to be thrown for any exception thrown from the brokered service. The original exception can be found in the <xref:System.Exception.InnerException%2A>.
131
131
This is natural behavior for a remotely hosted service because it is behavior from <xref:StreamJsonRpc.JsonRpc>.
132
132
When the service is local, the local proxy wraps all exceptions in the same way so that the client code can have just one exception path that works for local and remote services.
133
-
- Check the <xref:StreamJsonRpc.RemoteInvocationException.ErrorCode%2A> property if the service documentation suggests that specific codes will be set based on specific conditions that you can branch on.
133
+
- Check the <xref:StreamJsonRpc.RemoteInvocationException.ErrorCode%2A> property if the service documentation suggests that specific codes are set based on specific conditions that you can branch on.
134
134
- A broader set of errors is communicated by catching <xref:StreamJsonRpc.RemoteRpcException>, which is the base type for the <xref:StreamJsonRpc.RemoteInvocationException>.
135
135
- Expect <xref:StreamJsonRpc.ConnectionLostException> to be thrown from any call when the connection to a remote service drops or the process hosting the service crashes.
136
-
This is primarily of concern when the service may be acquired remotely.
136
+
This is primarily of concern when the service can be acquired remotely.
137
137
138
138
## Caching of the proxy
139
139
140
140
There is some expense in the activation of a brokered service and associated proxy, particularly when the service comes from a remote process.
141
-
When frequent use of a brokered service warrants caching of the proxy across many calls into a class, the proxy may be stored in a field on that class.
141
+
When frequent use of a brokered service warrants caching of the proxy across many calls into a class, the proxy can be stored in a field on that class.
142
142
The containing class should be disposable and dispose of the proxy inside its `Dispose` method.
143
143
Consider this example:
144
144
@@ -171,8 +171,8 @@ class MyExtension : IDisposable
171
171
}
172
172
```
173
173
174
-
The above code is roughly correct, but it does not account for race conditions between `Dispose` and `SayHiAsync`.
175
-
The code also does not account for <xref:Microsoft.ServiceHub.Framework.IServiceBroker.AvailabilityChanged> events that should lead to disposal of the proxy previously acquired and the reacquisition of the proxy the next time it is required.
174
+
The preceding code is roughly correct, but it doesn't account for race conditions between `Dispose` and `SayHiAsync`.
175
+
The code also doesn't account for <xref:Microsoft.ServiceHub.Framework.IServiceBroker.AvailabilityChanged> events that should lead to disposal of the proxy previously acquired and the reacquisition of the proxy the next time it's required.
176
176
177
177
The <xref:Microsoft.ServiceHub.Framework.ServiceBrokerClient> class is designed to handle these race and invalidation conditions to help keep your own code simple.
178
178
Consider this updated example that caches the proxy using this helper class:
@@ -204,7 +204,7 @@ class MyExtension : IDisposable
204
204
}
205
205
```
206
206
207
-
The above code is still responsible to dispose of the <xref:Microsoft.ServiceHub.Framework.ServiceBrokerClient> and each rental of a proxy.
207
+
The preceding code is still responsible to dispose of the <xref:Microsoft.ServiceHub.Framework.ServiceBrokerClient> and each rental of a proxy.
208
208
Race conditions between disposal and use of the proxy are handled by the <xref:Microsoft.ServiceHub.Framework.ServiceBrokerClient> object, which will dispose of each cached proxy at the time of its own disposal or when the last rental of that proxy has been released, whichever comes last.
209
209
210
210
### Important caveats regarding the `ServiceBrokerClient`
@@ -217,7 +217,7 @@ Consider using <xref:Microsoft.ServiceHub.Framework.IServiceBroker> directly in
217
217
The proxy is already cached beyond the scope of one method by the <xref:Microsoft.ServiceHub.Framework.ServiceBrokerClient>.
218
218
If you need greater control over the lifetime of the proxy, particularly around reacquisition due to an <xref:Microsoft.ServiceHub.Framework.IServiceBroker.AvailabilityChanged> event, use <xref:Microsoft.ServiceHub.Framework.IServiceBroker> directly instead and store the service proxy in a field.
219
219
220
-
- Create and store <xref:Microsoft.ServiceHub.Framework.ServiceBrokerClient> to a field rather than a local variable. If you create and use it as a local variable in a method, it is not adding any value over using <xref:Microsoft.ServiceHub.Framework.IServiceBroker> directly, but now you have to dispose of two objects (the client and the rental) instead of one (the service).
220
+
- Create and store <xref:Microsoft.ServiceHub.Framework.ServiceBrokerClient> to a field rather than a local variable. If you create and use it as a local variable in a method, it's not adding any value over using <xref:Microsoft.ServiceHub.Framework.IServiceBroker> directly, but now you have to dispose of two objects (the client and the rental) instead of one (the service).
221
221
222
222
### Choosing between IServiceBroker and ServiceBrokerClient
Manages lifetime of proxy|No. Owner must dispose of proxy when done using it.|Yes, they are kept alive and reused as long as they are valid.
230
+
Manages lifetime of proxy|No. Owner must dispose of proxy when done using it.|Yes, they're kept alive and reused as long as they're valid.
231
231
Applicable for stateless services|Yes|Yes
232
232
Applicable for stateful services|Yes|No
233
233
Appropriate when event handlers are added to proxy|Yes|No
234
234
Event to notify when old proxy is invalidated| <xref:Microsoft.ServiceHub.Framework.IServiceBroker.AvailabilityChanged> | <xref:Microsoft.ServiceHub.Framework.ServiceBrokerClient.Invalidated>
235
235
236
236
<xref:Microsoft.ServiceHub.Framework.ServiceBrokerClient> provides a convenient means for you to get fast and frequent reuse of a proxy, where you don't care if the underlying service is changed out from under you in between top-level operations.
237
-
238
237
But if you do care about those things and want to manage the lifetime of your proxies yourself, or you need event handlers (which implies you need to manage lifetime of the proxy), you should use <xref:Microsoft.ServiceHub.Framework.IServiceBroker>.
239
238
240
239
## Resilience to service disruptions
@@ -249,17 +248,17 @@ There are a few kinds of service disruptions that are possible with brokered ser
249
248
250
249
When a brokered service request can be satisfied by an available service but the service factory throws an unhandled exception, a <xref:Microsoft.ServiceHub.Framework.ServiceActivationFailedException> is thrown back to the client so they can understand and report the failure to the user.
251
250
252
-
When a brokered service request cannot be matched up with any available service, `null` is returned to the client.
251
+
When a brokered service request can't be matched up with any available service, `null` is returned to the client.
253
252
In such a case, <xref:Microsoft.ServiceHub.Framework.IServiceBroker.AvailabilityChanged> will be raised when and if that service becomes available later.
254
253
255
-
The service request may be declined not because the service isn't there, but because the version offered is lower than the version requested.
256
-
Your fallback plan may include retrying the service request with lower versions that your client knows exist and is able to interact with.
254
+
The service request might be declined not because the service isn't there, but because the version offered is lower than the version requested.
255
+
Your fallback plan might include retrying the service request with lower versions that your client knows exist and is able to interact with.
257
256
258
257
If/when latency from all the failed version checks becomes noticeable, the client can request the VisualStudioServices.VS2019_4Services.RemoteBrokeredServiceManifest to get a complete idea of what services and versions are available from a remote source.
### <aname="ServiceAvailabilityChanges"></a> Handling service availability changes
285
284
286
-
Brokered service clients may receive notifications of when they should requery for a brokered service they previously queried for by handling the <xref:Microsoft.ServiceHub.Framework.IServiceBroker.AvailabilityChanged> event.
287
-
Handlers to this event should be added *before* requesting a brokered service to ensure an event raised soon after a service request is made is not lost due to a race condition.
285
+
Brokered service clients can receive notifications of when they should requery for a brokered service they previously queried for by handling the <xref:Microsoft.ServiceHub.Framework.IServiceBroker.AvailabilityChanged> event.
286
+
Handlers to this event should be added *before* requesting a brokered service to ensure an event raised soon after a service request is made isn't lost due to a race condition.
288
287
289
-
When a brokered service is requested only for the duration of one async method's execution, handling this event is not recommended.
288
+
When a brokered service is requested only for the duration of one async method's execution, handling this event isn't recommended.
290
289
The event is most relevant to clients that store their proxy for extended periods such that they would need to compensate for service changes and are in a position to refresh their proxy.
291
290
292
-
This event may be raised on any thread, possibly concurrently to code that is using a service that the event is describing.
291
+
This event can be raised on any thread, possibly concurrently to code that is using a service that the event is describing.
293
292
294
-
Several state changes may lead to raising of this event, including:
293
+
Several state changes can lead to raising of this event, including:
295
294
296
295
- A solution or folder being opened or closed.
297
296
- A Live Share session starting.
@@ -302,13 +301,13 @@ An impacted brokered service only results in this event being raised to clients
302
301
The event is raised at most once per service after each request for that service.
303
302
For example if the client requests service **A** and service **B** experiences an availability change, no event will be raised to that client.
304
303
Later, when service **A** experiences an availability change, the client will receive the event.
305
-
If the client does not re-request service **A**, subsequent availability changes for **A** will not result in any further notifications to that client.
304
+
If the client doesn't re-request service **A**, subsequent availability changes for **A** will not result in any further notifications to that client.
306
305
Once the client requests **A** again, it becomes eligible to receive the next notification regarding that service.
307
306
308
307
The event is raised when a service becomes available, is no longer available, or experiences an implementation change that requires all prior service clients to requery for the service.
309
308
310
309
The <xref:Microsoft.ServiceHub.Framework.ServiceBrokerClient> handles availability change events regarding cached proxies automatically by disposing of the old proxies when any rentals have been returned and requesting a new instance of the service when and if its owner requests one.
311
-
This class can substantially simplify your code when the service is stateless and does not require that your code attaches event handlers to the proxy.
310
+
This class can substantially simplify your code when the service is stateless and doesn't require that your code attaches event handlers to the proxy.
312
311
313
312
## Retrieving a brokered service pipe
314
313
@@ -318,7 +317,7 @@ A pipe to the brokered service may be obtained via the <xref:Microsoft.ServiceHu
318
317
This method takes a <xref:Microsoft.ServiceHub.Framework.ServiceMoniker> instead of a <xref:Microsoft.ServiceHub.Framework.ServiceRpcDescriptor> because the RPC behaviors provided by a descriptor are not required.
319
318
When you have a descriptor, you can obtain the moniker from it via the <xref:Microsoft.ServiceHub.Framework.ServiceRpcDescriptor.Moniker%2A?displayProperty=nameWithType> property.
320
319
321
-
While pipes are bound to I/O they are not eligible for garbage collection.
320
+
While pipes are bound to I/O they're not eligible for garbage collection.
322
321
Avoid memory leaks by always completing these pipes when they will no longer be used.
323
322
324
323
In the following snippet, a brokered service is activated and the client has a direct pipe to it.
@@ -372,7 +371,7 @@ Mock<IFileSystem> mockFileSystem = new Mock<IFileSystem>();
The mocked brokered service container does not require a proffered service to be registered first as Visual Studio itself does.
374
+
The mocked brokered service container doesn't require a proffered service to be registered first as Visual Studio itself does.
376
375
377
376
Your code under test can acquire the brokered service as normal, except that under the test it will get your mock instead of the real one it would get while running under Visual Studio:
0 commit comments