Can someone explain to me what's the proper usage of gRPC StreamObserver.onError? - error-handling

I am trying to handle gRPC errors properly (Java, Spring-boot app).
Basically, I need too transfer error details from gRPC server to client, but I find it hard to understand the proper usage of StreamObserver.onError();
The method doc says:
"Receives a terminating error from the stream. May only be called once
and if called it must be the last method called. In particular if an
exception is thrown by an implementation of onError no further calls
to any method are allowed."
What does this "no further calls are allowed" mean? In the app that I maintain, they call other gRPC methods and they get java.lang.IllegalStateException: call already closed which is just fine, as per documentation.
I am wondering - should I (the developer) terminate the current java method (which usus gRPC calls) after an error is received? Like for example throwing an exception to stop execution. Or it is expected tht gRPC is going to terminate the execution.. (something like throwing an exception from gRPC)
Basically how do I properly use onError() and what should I expect and handle if I call it?
I need an explanation of its usage and effects.

There are two StreamObserver instances involved. One is for the inbound direction, which is the StreamObserver instance you implement and pass to the gRPC library. This is the StreamObserver containing your logic for how to handle responses. The other is for the outbound direction, which is the StreamObserver instance that gRPC library returns to you when calling the RPC method. This is the StreamObserver that you use to send requests. Most of the time, these two StreamObservers are interacting with each other (e.g., in a fully duplexed streaming call, the response StreamObserver usually calls the request StreamObserver's onNext() method, this is how you achieve ping-pong behavior).
"no further calls are allowed" means you should not call any more onNext(), onComplete() and/or onError() on the outbound direction StreamObserver when the inbound StreamObserver's onError() method is invoked, even if your implementation for the inbound onError() throws an exception. Since the inbound StreamObserver is invoked asynchronously, it has nothing to do with your method that encloses the StreamObserver's implementation.
For example:
public class HelloWorld {
private final HelloWorldStub stub;
private StreamObserver<HelloRequest> requestObserver;
...
private void sendRequest(String message) {
requestObserver.onNext(HelloRequest.newBuilder.setMessage(message).build());
}
public void start() {
stub.helloWorld(new StreamObserver<HelloResponse> {
#Override
public void onNext(HelloResponse response) {
sendRequest("hello from client");
// Optionally you can call onCompleted() or onError() on
// the requestObserver to terminate the call.
}
#Override
public void onCompleted() {
// You should not call any method on requestObserver.
}
#Override
public void onError(Throwable error) {
// You should not call any method on requestObserver.
}
});
}
}
It has nothing to do with the start() method.
The doc is also mentioning that you should not do things like
try {
requestObserver.onCompleted();
} catch(RuntimeException e) {
requestObserver.onError();
}
It's mostly for user's own StreamObserver implementations. StreamObserver's returned by gRPC never throws.

I've extracted a template for GRPC streaming which sort of abstracts away a lot of the GRPC boilerplate that also addresses the the logic for onError. In the DechunkingStreamObserver
I use the following general pattern for GRPC streaming which is something along the lines of
META DATA DATA DATA META DATA DATA DATA
An example of where I would use it would be to take one form and transform it to another form.
message SavedFormMeta {
string id = 1;
}
message SavedFormChunk {
oneof type {
SavedFormMeta meta = 1;
bytes data = 2;
}
}
rpc saveFormDataStream(stream SavedFormChunk) returns (stream SavedFormChunk) {}
I use a flag that would track the inError state to prevent further processing and catch exceptions on the onNext and onComplete both of which I redirect to onError which forwards the error to the server side.
The code below pulls the GRPC semantics and takes lamdas that do the processing.
/**
* Dechunks a GRPC stream from the request and calls the consumer when a complete object is created. This stops
* further processing once an error has occurred.
*
* #param <T> entity type
* #param <R> GRPC chunk message type
* #param <S> GRPC message type for response streams
*/
class DechunkingStreamObserver<T, R, S> implements StreamObserver<R> {
/**
* This function takes the current entity state and the chunk and returns a copy of the combined result. Note the combiner may modify the existing data, but may cause unexpected behaviour.
*/
private final BiFunction<T, R, T> combiner;
/**
* A function that takes in the assembled object and the GRPC response observer.
*/
private final BiConsumer<T, StreamObserver<S>> consumer;
/**
* Predicate that returns true if it is a meta chunk indicating a start of a new object.
*/
private final Predicate<R> metaPredicate;
/**
* this function gets the meta chunk and supplies a new object.
*/
private final Function<R, T> objectSupplier;
/**
* GRPC response observer.
*/
private final StreamObserver<S> responseObserver;
/**
* Currently being processed entity.
*/
private T current = null;
/**
* In error state. Starts {#code false}, but once it is set to {#code true} it stops processing {#link #onNext(Object)}.
*/
private boolean inError = false;
/**
* #param metaPredicate predicate that returns true if it is a meta chunk indicating a start of a new object.
* #param objectSupplier this function gets the meta chunk and supplies a new object
* #param combiner this function takes the current entity state and the chunk and returns a copy of the combined result. Note the combiner may modify the existing data, but may cause unexpected behaviour.
* #param consumer a function that takes in the assembled object and the GRPC response observer.
* #param responseObserver GRPC response observer
*/
DechunkingStreamObserver(
final Predicate<R> metaPredicate,
final Function<R, T> objectSupplier,
final BiFunction<T, R, T> combiner,
final BiConsumer<T, StreamObserver<S>> consumer,
final StreamObserver<S> responseObserver) {
this.metaPredicate = metaPredicate;
this.objectSupplier = objectSupplier;
this.combiner = combiner;
this.consumer = consumer;
this.responseObserver = responseObserver;
}
#Override
public void onCompleted() {
if (inError) {
return;
}
try {
if (current != null) {
consumer.accept(current, responseObserver);
}
responseObserver.onCompleted();
} catch (final Exception e) {
onError(e);
}
}
#Override
public void onError(final Throwable throwable) {
responseObserver.onError(throwable);
inError = true;
}
#Override
public void onNext(final R chunk) {
if (inError) {
return;
}
try {
if (metaPredicate.test(chunk)) {
if (current != null) {
consumer.accept(current, responseObserver);
}
current = objectSupplier.apply(chunk);
} else {
current = combiner.apply(current, chunk);
}
} catch (final Exception e) {
onError(e);
}
}
}
I have 4 lamdas
Predicate<R> metaPredicate which takes in a chunk and returns whether the chunk is meta or not.
Function<R, T> objectSupplier which takes in a meta chunk and creates a new object that is used by your module.
BiFunction<T, R, T> combiner, which takes in a data chunk and the current object and returns a new object that contains the combination.
BiConsumer<T, StreamObserver<S>> consumer which will consume a completed object. It also passes in a stream observer in the case of sending new objects in response.

the only thing you want to do is mark as return after calling the responseObserver.onError(); like below. because there is nothing to do after sending the error.
if(condition){
responseObserver.onError(StatusProto.toStatusException(status));
//this is the required part
return;
}else{
responseObserver.onComplete(DATA);
}

Related

Mono.zip request fails when subscribed

I have the following piece of code, where externalgetcall is a GET request to a external service asking for some data
myservice.externalgetcall(id).blockOptional();
this code works, but if i get rid of the blockOptional and write the following, externalgetcall fails with a java.lang.NullPointerException:
java.lang.NullPointerException: null
at org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository.saveAuthorizedClient(HttpSessionOAuth2AuthorizedClientRepository.java:63) ~[spring-security-oauth2-client-5.7.2.jar:5.7.2]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ? Request to GET
myservice.externalgetcall(id).subscribe();
moreover, if I do this, the blocking one right before the non blocking one, it works, so it clearly has to do with Oauth not completing somehow if the call is non blocking:
myservice.externalgetcall(id).blockOptional();
myservice.externalgetcall(id).subscribe();
externalgetcall(id)
public Mono<MyClass> externalgetcall(String id) {
logger.debug("getting contact: {}", id);
return this.webClient
.get()
.uri(externaluri)
.retrieve()
.bodyToMono(MyClass.class)
.doOnNext(myClass -> logger.debug("success {}", myClass))
.doOnError(throwable -> logger.error("error : ", throwable))
}
it fails at this point:
it looks like it happens when trying to run the setAttribute method,when debugging i can see this:
this = {FluxSubscribeOnCallable$CallableSubscribeOnSubscription#13199} size = 1
Unable to evaluate the expression Method threw 'java.lang.UnsupportedOperationException' exception.
public final class HttpSessionOAuth2AuthorizedClientRepository implements OAuth2AuthorizedClientRepository {
private static final String DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME = HttpSessionOAuth2AuthorizedClientRepository.class.getName() + ".AUTHORIZED_CLIENTS";
private final String sessionAttributeName;
public HttpSessionOAuth2AuthorizedClientRepository() {
this.sessionAttributeName = DEFAULT_AUTHORIZED_CLIENTS_ATTR_NAME;
}
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal, HttpServletRequest request, HttpServletResponse response) {
if (this.isPrincipalAuthenticated(principal)) {
this.authorizedClientService.saveAuthorizedClient(authorizedClient, principal);
} else {
this.anonymousAuthorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, request, response);
}
}
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal, HttpServletRequest request, HttpServletResponse response) {
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
Assert.notNull(request, "request cannot be null");
Assert.notNull(response, "response cannot be null");
Map<String, OAuth2AuthorizedClient> authorizedClients = this.getAuthorizedClients(request);
authorizedClients.put(authorizedClient.getClientRegistration().getRegistrationId(), authorizedClient);
request.getSession().setAttribute(this.sessionAttributeName, authorizedClients);
}
This is not much context to come to a rock-solid explanation, but the NPE leads me to believe that this is a case of race condition.
Something to consider is the fact that you are replacing a blocking terminal operation with an asynchronous terminal operation on the publisher. blockOptional and subscribe both initiate a subscription, but in the latter case, execution will not wait at that point for the publisher's onComplete signal.
Again, hard to tell without the complete code, but my guess is that whatever code comes after this snippet is using some data that is populated or hydrated as a result of this publisher. Using blockOptional ensures that the publisher completes before this happens, while subscribe does not.

Checking for errors/exception while using await in ListenableFuture Kotlin

I am new to Kotlin, and while I am aware of the concept about ListenableFuture, and how we can add callbacks to it for checking for success or failure.
However, I am trying to use await (https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-guava/kotlinx.coroutines.guava/await.html) feature.
There is a library function I am calling and this is how it looks like:
#NonNull
ListenableFuture<Void> removeAsync(#NonNull String queryExpression,
#NonNull SearchSpec searchSpec);
/**
* #deprecated use {#link #removeAsync}
*
* #param queryExpression Query String to search.
* #param searchSpec Spec containing schemaTypes, namespaces and query expression
* indicates how document will be removed. All specific about how to
* scoring, ordering, snippeting and resulting will be ignored.
* #return The pending result of performing this operation.
*/
#NonNull
#Deprecated
default ListenableFuture<Void> remove(#NonNull String queryExpression,
#NonNull SearchSpec searchSpec) {
return removeAsync(queryExpression, searchSpec);
}
Now, I am calling this function like this:
suspend fun removeDocs() {
val removeQuery = SearchSpec.Builder().addFilterSchemas("MyDocument").build()
appSearchSession.removeAsync("", removeQuery).await()
}
Now, I don't understand how do I check for the success of this operation? What sort of error handling can I do in removeDocs function?
Coroutines allow to execute asynchronous operations using classic sequential code. Then we don't have to use callbacks or check for some flags in order to handle exceptions. Exceptions are handled directly - by throwing them.
If await() didn't throw an exception, that means the operation succeeded:
appSearchSession.removeAsync("", removeQuery).await()
// if we get here, remove succeeded
If you have to react somehow to the failed operation, use try...catch:
try {
appSearchSession.removeAsync("", removeQuery).await()
// remove succeeded
catch (e: Throwable) {
// remove failed
}

Spring Cloud Stream deserialization error handling for Batch processing

I have a question about handling deserialization exceptions in Spring Cloud Stream while processing batches (i.e. batch-mode: true).
Per the documentation here, https://docs.spring.io/spring-kafka/docs/2.5.12.RELEASE/reference/html/#error-handling-deserializer, (looking at the implementation of FailedFooProvider), it looks like this function should return a subclass of the original message.
Is the intent here that a list of both Foo's and BadFoo's will end up at the original #StreamListener method, and then it will be up to the code (i.e. me) to sort them out and handle separately? I suspect this is the case, as I've read that the automated DLQ sending isn't desirable for batch error handling, as it would resubmit the whole batch.
And if this is the case, what if there is more than one message type received by the app via different #StreamListener's, say Foo's and Bar's. What type should the value function return in that case? Below is the pseudo code to illustrate the second question?
#StreamListener
public void readFoos(List<Foo> foos) {
List<> badFoos = foos.stream()
.filter(f -> f instanceof BadFoo)
.map(f -> (BadFoo) f)
.collect(Collectors.toList());
// logic
}
#StreamListener
public void readBars(List<Bar> bars) {
// logic
}
// Updated to return Object and let apply() determine subclass
public class FailedFooProvider implements Function<FailedDeserializationInfo, Object> {
#Override
public Object apply(FailedDeserializationInfo info) {
if (info.getTopics().equals("foo-topic") {
return new BadFoo(info);
}
else if (info.getTopics().equals("bar-topic") {
return new BadBar(info);
}
}
}
Yes, the list will contain the function result for failed deserializations; the application needs to handle them.
The function needs to return the same type that would have been returned by a successful deserialization.
You can't use conditions with batch listeners. If the list has a mixture of Foos and Bars, they all go to the same listener.

How to Junit test servlet filter which has specific response

What is the best way to unit test this code? I need to establish a consistent check for httpResponse which sendError() when condition is true. Thanks in advance!
Edit: Unfortunately, this filter is not with Spring MVC so my choice is limited.
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain filterchain) throws IOException, ServletException {
String ipAddress = request.getRemoteAddr();
if( SomeParameterCheckingFunction ((request)) ) {
logger.error("Error detected! Time: " + new Date().toString() + ", Originating IP: " + ipAddress);
if (response instanceof HttpServletResponse){
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN,"You are not allowed to access the server!");
}
}
filterchain.doFilter(request, response);
}
For example when using the Mockito mock framework, the provided doFilter() method could be JUnit tested using below test case:
#Test
public void testDoFilter() throws IOException, ServletException {
// create the objects to be mocked
HttpServletRequest httpServletRequest = mock(HttpServletRequest.class);
HttpServletResponse httpServletResponse = mock(HttpServletResponse.class);
FilterChain filterChain = mock(FilterChain.class);
// mock the getRemoteAddr() response
when(httpServletRequest.getRemoteAddr()).thenReturn("198.252.206.16");
AccessFilter accessFilter = new AccessFilter();
accessFilter.doFilter(httpServletRequest, httpServletResponse,
filterChain);
// verify if a sendError() was performed with the expected values
verify(httpServletResponse).sendError(HttpServletResponse.SC_FORBIDDEN,
"You are not allowed to access the server!");
}
One option might be to pull the definition of SomeParameterCheckingFunction out of the filter itself (and consume it in the filter). Then, you could separately unit test that logic. But, I'm not sure this will be sufficient for you.
Another option might be to test the filter class using Mockito. This would involve some labor, mocking the request, response, filterchain, logging, and such. You should be able to verify the response.sendError() call afterwards something like so (assumes static import of Mockito.* methods):
//setup mock with concrete class for instanceof check
ServletResponse resp = mock(HttpServletResponse.class);
//define behavior for desired method with void return type. this appears optional for mocks.
//doNothing().when(resp).sendError(anyInt(), anyString());
//your test here, passing in resp
if(resp instanceof HttpServletResponse) resp.sendError(400, "blah");
//verify method was called
verify(resp).sendError(400, "blah");
There may or may not be a way to test your filter with HttpUnit, but that might not meet your desire for a proper unit test.
Updated Feb 2018: OpenBrace Limited has closed down, and its ObMimic product is no longer supported.
Here's an example of how you could test the "sendError" using my ObMimic library of out-of-container test-doubles for the Servlet API.
First, some notes on the given doFilter code:
"((request))" is presumably meant to just be "(request)".
The nature of "logger" isn't shown, so I've ignored that for now as irrelevant.
The filter as given will check non-HTTP requests but if they're rejected the usual log message is written but then sendError is skipped and the normal processing carried out instead. Depending on what you're trying to do, you might want to reject non-HTTP requests up-front, or not check them at all, or do some other type of rejection in place of "sendError". For now I'm assuming we're only interested in how to test "sendError" on HTTP requests.
The example test below has shown that even when sendError is called the processing still drops through to the normal "doFilter" call. Assuming this isn't intentional, the "sendError" should be followed by a "return", or "doFilter" should be in an "else" clause.
For the example code below:
I've used JUnit as the test framework, but it could just as well be TestNG or anything else.
For "SomeParameterCheckingFunction", I've just used one that returns true if the remote IP address is "1.1.1.1".
"sendError" produces an HTML response that displays the given message. For this example I just check that the response body contains the given message. If you instead wanted to check what parameter values were passed to "sendError", the "Professional Edition" of ObMimic has a "history" feature that would let you do that. Or you could do a "sendError" on a separate response instance and then check that the response bodies match exactly.
Anyway, here's the example code:
package com.openbrace.experiments.examplecode.stackoverflow13365536;
import static org.junit.Assert.*;
import com.openbrace.experiments.examplecode.stackoverflow13365536.YourFilter;
import com.openbrace.obmimic.mimic.servlet.FilterChainMimic;
import com.openbrace.obmimic.mimic.servlet.FilterConfigMimic;
import com.openbrace.obmimic.mimic.servlet.ServletMimic;
import com.openbrace.obmimic.mimic.servlet.http.HttpServletRequestMimic;
import com.openbrace.obmimic.mimic.servlet.http.HttpServletResponseMimic;
import com.openbrace.obmimic.support.servlet.EndPoint;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Example tests for {#link YourFilter#doFilter(ServletRequest, ServletResponse,
* FilterChain)}.
*
* #author Mike Kaufman, OpenBrace Limited
*/
public class YourFilterTest {
/** The filter to be tested by this instance's test. */
private YourFilter filter;
/** The "mimic" request to be used in this instance's test. */
private HttpServletRequestMimic request;
/** The "mimic" response to be used in this instance's test. */
private HttpServletResponseMimic response;
/** The filter chain to be used in this instance's test. */
private FilterChainMimic filterChain;
/**
* Set up for this instance's test by creating an initialized filter and a
* request, response and filter chain for use in the test.
*
* #throws ServletException if the filter's init method throws such an
* exception.
*/
#Before
public void setUp() throws ServletException {
/*
* Note that for this example's simple filter and tests:
* - We don't need anything particular in the filter's FilterConfig.
* - We don't need any particular ServletContext, so we can leave
* ObMimic to use its default ServletContext throughout.
* - We don't need to retain any references to the filter's FilterConfig
* or ServletContext.
* - We can use a FilterChainMimic with default values as the filter
* chain. This has a ServletMimic as the next thing in the chain, and
* ServletMimic keeps a count of the calls to its service method, so
* we can use this to check whether the filter chain's doFilter has
* been invoked.
*/
filter = new YourFilter();
filter.init(new FilterConfigMimic());
request = new HttpServletRequestMimic();
response = new HttpServletResponseMimic();
filterChain = new FilterChainMimic();
}
/**
* Test the doFilter method with an example request for which
* SomeParameterCheckingFunction returns true (so that the FORBIDDEN
* response should result).
*
* #throws ServletException if the servlet throws such an exception.
* #throws IOException if the servlet throws such an exception.
*/
#Test
public void testYourFilterWithForbiddenRequest()
throws ServletException, IOException {
// Configure the request so that SomeParameterCheckingFunction will
// return true, which for purposes of this example is triggered by a
// particular "remote address".
request.getMimicState().setRemoteEndPoint(
new EndPoint(null, "1.1.1.1", null));
// Invoke the doFilter method.
filter.doFilter(request, response, filterChain);
// Check that the response is now FORBIDDEN, and that its HTML content
// does include the expected text message.
int responseCode = response.getMimicState().getHttpStatusCode();
String responseBody = response.getMimicState().getBodyContentAsString();
String expectedMessage = "You are not allowed to access the server!";
assertEquals("Response has incorrect status code",
HttpServletResponse.SC_FORBIDDEN, responseCode);
Assert.assertThat("FORBIDDEN response does not include expected message",
responseBody, CoreMatchers.containsString(expectedMessage));
// Check that the filter chain was not invoked. As we've used a
// FilterChainMimic with default values, its "target" is a ServletMimic,
// so we can just check if there have been any calls to that Servlet.
ServletMimic targetServlet
= (ServletMimic) filterChain.getMimicState().getTarget();
boolean filterChainInvoked
= targetServlet.getMimicState().getServiceCount() > 0;
assertFalse("FORBIDDEN response but filterChain.doFilter still called",
filterChainInvoked);
}
}
If you want to try it, there are full details of ObMimic and a free download at the ObMimic website.

How can I wait for an async WCF service to complete?

The question pretty much sums it up. I have a WCF service, and I want to wait until it finished to do something else, but it has to be until it finishes. My code looks something like this. Thanks!
private void RequestGeoCoordinateFromAddress(string address)
{
GeocodeRequest geocodeRequest = new GeocodeRequest();
GeocodeServiceClient geocodeService = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
geocodeService.GeocodeCompleted += new EventHandler<GeocodeCompletedEventArgs>(geocodeService_GeocodeCompleted);
// Make the geocode request
geocodeService.GeocodeAsync(geocodeRequest);
//if (geocodeResponse.Results.Length > 0)
// results = String.Format("Latitude: {0}\nLongitude: {1}",
// geocodeResponse.Results[0].Locations[0].Latitude,
// geocodeResponse.Results[0].Locations[0].Longitude);
//else
// results = "No Results Found";
// wait for the request to finish here, so I can do something else
// DoSomethingElse();
}
private void geocodeService_GeocodeCompleted(object sender, GeocodeCompletedEventArgs e)
{
bool isErrorNull = e.Error == null;
Exception error = e.Error;
try
{
double altitude = e.Result.Results[0].Locations[0].Latitude;
double longitude = e.Result.Results[0].Locations[0].Longitude;
SetMapLocation(new GeoCoordinate(altitude, longitude));
}
catch (Exception ex)
{
// TODO: Remove reason later
MessageBox.Show("Unable to find address. Reason: " + ex.Message);
}
}
There is a pattern, supported by WCF, for a call to have an asynchronous begin call, and a corresponding end call.
In this case, the asynchronous methods would be in the client's interface as so:
[ServiceContract]
interface GeocodeService
{
// Synchronous Operations
[OperationContract(AsyncPattern = false, Action="tempuri://Geocode", ReplyAction="GeocodeReply")]
GeocodeResults Geocode(GeocodeRequestType geocodeRequest);
// Asynchronous operations
[OperationContract(AsyncPattern = true, Action="tempuri://Geocode", ReplyAction="GeocodeReply")]
IAsyncResult BeginGeocode(GeocodeRequestType geocodeRequest, object asyncState);
GeocodeResults EndGeocode(IAsyncResult result);
}
If you generate the client interface using svcutil with the asynchronous calls option, you will get all of this automatically. You can also hand-create the client interface if you aren't using automatically generating the client proxies.
The End call would block until the call is complete.
IAsyncResult asyncResult = geocodeService.BeginGeocode(geocodeRequest, null);
//
// Do something else with your CPU cycles here, if you want to
//
var geocodeResponse = geocodeService.EndGeocode(asyncResult);
I don't know what you've done with your interface declarations to get the GeocodeAsync function, but if you can wrangle it back into this pattern your job would be easier.
You could use a ManualResetEvent:
private ManualResetEvent _wait = new ManualResetEvent(false);
private void RequestGeoCoordinateFromAddress(string address)
{
...
_wait = new ManualResetEvent(false);
geocodeService.GeocodeAsync(geocodeRequest);
// wait for maximum 2 minutes
_wait.WaitOne(TimeSpan.FromMinutes(2));
// at that point the web service returned
}
private void geocodeService_GeocodeCompleted(object sender, GeocodeCompletedEventArgs e)
{
...
_wait.Set();
}
Obviously doing this makes absolutely no sense, so the question here is: why do you need to do this? Why using async call if you are going to block the main thread? Why not use a direct call instead?
Generally when using async web service calls you shouldn't block the main thread but do all the work of handling the results in the async callback. Depending of the type of application (WinForms, WPF) you shouldn't forget that GUI controls can only be updated on the main thread so if you intend to modify the GUI in the callback you should use the appropriate technique (InvokeRequired, ...).
Don't use this code with Silverlight:
private ManualResetEvent _wait = new ManualResetEvent(false);
private void RequestGeoCoordinateFromAddress(string address)
{
...
_wait = new ManualResetEvent(false);
geocodeService.GeocodeAsync(geocodeRequest);
// wait for maximum 2 minutes
_wait.WaitOne(TimeSpan.FromMinutes(2));
// at that point the web service returned
}
private void geocodeService_GeocodeCompleted(object sender, GeocodeCompletedEventArgs e)
{
...
_wait.Set();
}
When we call _wait.WaitOne(TimeSpan.FromMinutes(2)), we are blocking the UI thread, which means the service call never takes place. In the background, the call to geocodeService.GeocodeAsync is actually placed in a message queue, and will only be actioned when the thread is not executing user code. If we block the thread, the service call never takes place.
Synchronous Web Service Calls with Silverlight: Dispelling the async-only myth
The Visual Studio 11 Beta inludes C# 5 with async-await.
See Async CTP - How can I use async/await to call a wcf service?
It makes it possible to write async clients in a 'synchronous style'.
I saw one guy did use ManualReset and waitAll, but he had to wrap all code inside of ThreadPool..
It is very bad idea...thought it works