0

I need to modify a WCF method to run asynchronously. [OperationContract(IsOneWay=true)] is not a valid option in my case as the client expects a return value. I do not have control over the client's consumption of the existing WCF method call and I cannot change the signature to return void. I also cannot modify the client to force an asynchronous WCF call. The client will still have to expect to call its synchronous WCF call and get its hard-coded response.

Within the service, there are behaviors in place to manage monitoring and exceptions. The option that I was considering would be to have the WCF service self-register or self-discover its endpoint, add a One-Way WCF method to the service, and have the synchronous method call the One-Way method over WCF. I felt that would be the safest way to ensure that all of the existing behaviors still applied. The behaviors don't collect or depend on any information from the end client. While it seems technically possible to start other threads or asynchronous calls from within the existing synchronous call, I don't like that as I believe it could corrupt state information that, at this point, is tied to the current WCF OperationContext.

It seems that all options that I've explored related to this require some sort of compromise and that the option of having the synchronous call forwarded over to the One-Way call would be the safest. Is there a better way to go about this or is it possible to force a WCF method to give the same immediate return to a client as a OneWay call, but include a return value?

Alan Samet
  • 1,118
  • 2
  • 15
  • 18
  • if the signature returns void, is it not possible to throw the method body in a Task.Run(), or use DataFlow from the TPL? – Mike_G Sep 23 '16 at 20:28
  • My concern is that there is workflow information that is used in the exception and status monitoring behaviors. There is a stateful object that is stored in the OperationContext that maintains information about the process. If, for instance, there is an exception, it will save all of the relevant process information as a package. I don't know if OperationContext would stick around with something that's kicked off through TPL. – Alan Samet Sep 23 '16 at 20:33

1 Answers1

1

There's a couple ways to go about this. You've already identified one of them ("Chained WCF Calls"). The other does indeed involve TPL techniques, and finesse.

Chained WCF Calls.

With this approach, just remember the most expensive part of WCF interactions is the (de-)serialization of payload / parameters. In terms of latency, your client will pay that price twice because you've "chained" your endpoints together. The "oneway" call configuration still requires that the client "wait" until all parameters have deserialized correctly and the call "looks good." This is because oneway calls still have the ability to raise a SOAP exception back to the caller indicating illegal parameters, or crashed WCF behaviors, etc. Only the latency of the "endpoint work" (the part you program) will be "saved" by using this technique.

With this approach, you must also consider that your oneway routine must be configured with most or all the same behaviors as your original endpoint. This is particularly true for the exception handling aspect. You said:

There is a stateful object that is stored in the OperationContext that maintains information about the process. If, for instance, there is an exception, it will save all of the relevant process information as a package.

It sounds like you would want to strip all or most of the behaviors from the original entry point, and instead have them decorate your internal oneway routine.

TPL Finesse

Note: this next approach likely solves only half your problem...
because it does not address the WCF exception-handing scenario 
you called out.

In general, because it is [ThreadStatic] you are correct to be concerned about OperationContext not flowing between threads and TPL routines. However, according to MSDN, the OperationContext.Current property is public. Using closures, you should be able manually assign the OperationContext from the WCF request thread onto a new OperationContext.Current of a new Task.

In this case, you could allow the primary WCF request thread to return...and yet the new Task could run with the same OperationContext reference. If an exception is thrown on the new Task, it certainly will not be handled by a custom WCF behavior, since the primary WCF request thread would presumably have already exited-and-returned long ago, and thus it already "passed through" all the (exception handling) behavior layers.

In short, the only thing this buys you is if a WCF behavior had stamped a transaction ID into the OperationContext, then that transaction ID would manually flow onto the new Task...allowing a down-stream routine to pick it up.

If you were to follow this approach then:

  • Try to ensure there is still a callback waiting for the background Task...its purpose is to catch any exception, log it if necessary, and swallow. You don't want independent Task in your WCF app having exceptions that are unhandled. Remember, you will have no help from WCF behaviors in this case.
  • Be sure that the Task has a fairly confined latency...such as 400 to 800ms. You don't want a Task to run for 5 minutes. Presumably your WCF app is IIS hosted, and IIS can reload or unload your WCF AppDomain without warning...because it has no idea that you've created a new Task that is still doing work. As long as the latency is constrained, I doubt you will run into issues with IIS shutting down your WCF app "too soon."

Finally, if your situation is more complex than this, be aware of custom TaskScheduler instances.

Community
  • 1
  • 1
Brent Arias
  • 29,277
  • 40
  • 133
  • 234