'What's the best way to cancel an asynchronous WCF request?
(Assuming a WCF method called "MyFunction")
Currently, to support canceling a WCF request, I'm using the BeginMyFunction/EndMyFunction methods generated by svcutil (and handling an isCanceled flag when dispatching the results to the main thread). I'd like to use the MyFunctionAsync method (and hooking into MyFunctionAsyncCompleted event instead) for async calls instead of Begin/End.
What is the best/supported way to handle canceling WCF requests if using MyFunctionAsyncCompleted, and still ensuring that the event doesn't get fired on a page that's no longer loaded (i.e. page navigation within a frame).
Thanks!
EDIT:
I've decided that I want to create my WcfClient object on a per-call basis (as opposed to per-WPF-Page or per-Application), so here's what I've come up with:
public void StartValidation(){
WcfClient wcf = new WcfClient();
wcf.IsValidCompleted += new EventHandler<IsValidCompletedEventArgs>(wcf_IsValidCompleted);
//pass the WcfClient object as the userState parameter so it can be closed later
wcf.IsValidAsync(TextBox.Text, wcf);
}
void wcf_IsValidCompleted(object sender, IsValidCompletedEventArgs e) {
if(!m_IsCanceled){
//Update the UI
//m_IsCanceled is set to true when the page unload event is fired
}
//Close the connection
if (e.UserState is WcfClient) {
((WcfClient)e.UserState).Close();
}
}
I'm finding it difficult to figure out what the recommended way to accomplish what I've just implemented is. Is this fine as-is, or are there pitfalls/edge cases that I need to worry about? What's the golden standard when it comes to properly canceling a WCF call?
Solution 1:[1]
There is no way to cancel the asynchronous request unless you manually create the asynchronous functions. Considering that you are auto generating your WCF calls that would make it more of a chore. Even then like you said the call does not cancel it will still run it's course. If you still want to have a cancel you just have to make sure the client/UI ignores the results from the call.
Solution 2:[2]
Easiest way I know of to do this with a WCF Client and the Task based async pattern is to register an Abort action with the cancellation token
private async Task CallOrAbortMyServiceAsync(CancellationToken cancellation)
{
var client = new SomeServiceClient();
await using var registration = cancellation.Register(client.Abort);
try
{
await client.CallMyServiceAsync();
} catch (CommunicationObjectAbortedException) {
// This will be called when you are cancelled, or some other fault.
}
}
Solution 3:[3]
.Net 4.5
- Every WCF call implementation creates a CancellationTokenSource and stores it in a location accessible to all WCF services. I.e. in single server environments can be in an in memory cache.
- CancellationTokenSource.Token is passed to all method calls initiated by WCF method, including calls to databases and network calls where applicable.
- The WCF method can have a unique identifier as parameter or can return a unique identifier what is associated with CancellationTokenSource.
- When client needs to cancel the operation calls a WCF method and passes in the unique identifier of previous call.
- CancellationTokenSource is retrieved using the unique identifier and its Cancel method is invoked. If cancellation token is properly handled the operation started by previous call will soon be cancelled and can either return OperationCanceledException or a CancelFault or some other fault what signals client that call was cancelled.
- When client calls cancel at step 4 at the same time can abort the original WCF call. However if clients properly handle OperationCanceledException or CancelFault this isn't needed. OperationCanceledException can even bubble up to an ajax call if original call was started from a web page.
Something similar can be implemented in older .Net frameworks too where CancellationToken is not yet available.
Solution 4:[4]
Cancelling a request isn't really practical in the general sense simply due to the asynchronous nature of the request you're talking about. Specific workarounds can be performed. E.g., adding a new Cancel signal to your protocol that sets some shared state that your primary, long-running task periodically checks to verify that it should continue executing.
Either way, once again due to the asynchronous nature of the request, I believe that it's still the client's responsibility to know to ignore results from a request that has been cancelled. It isn't possible to guarantee that the client won't receive a response from a request that it's just cancelled simply because a race exists between the client and the server. It's possible to add an additional layer that monitors whether something has been cancelled and knows to throw away a request's response, but that's not preventing the result from being transmitted to the client.
Solution 5:[5]
If I understand you correctly you are trying to correctly abort a pending call to a WCF service. You want to use the MyFunctionCompleted
event because it is handled in the UI Thread.
What you probably should do is call the Abort
method on the WcfClient
(you need to keep a reference to it). This will cleanup resources on the client side. The server will stil finish the request, but the client will not wait on it any more. Shortly after the MyFunctionCompleted
event is triggered. By checking the client.State
you will know whether the call succeeded, failed or was aborted.
Here is a small test app having a Send button, a Abort button and a textbox for the results:
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private SomeServiceClient m_client;
private void buttonSend_Click(object sender, EventArgs e)
{
m_client = new SomeServiceClient();
m_client.MyFunctionCompleted += new EventHandler<MyFunctionCompletedEventArgs>(client_MyFunctionCompleted);
m_client.MyFunctionAsync(4000, m_client);
}
private void buttonAbout_Click(object sender, EventArgs e)
{
if( m_client != null )
m_client.Abort();
}
void client_MyFunctionCompleted(object sender, MyFunctionCompletedEventArgs e)
{
var client = e.UserState as SomeServiceClient;
if (client != null)
{
if (client.State == System.ServiceModel.CommunicationState.Opened)
{
textBox.Text += string.Format("Result: {0}\r\n", e.Result);
client.Close();
return;
}
else if (client.State == System.ServiceModel.CommunicationState.Faulted)
{
textBox.Text += string.Format("Error: {0}\r\n", e.Error.Message);
}
client.Abort();
}
}
}
There is no exception handling and cleaning up... I don't know if it is the recommended way, but I think calling abort is the right way. You need to handle the different error situations any way.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | bju1046 |
Solution 2 | delixfe |
Solution 3 | user3285954 |
Solution 4 | Greg D |
Solution 5 | BasvdL |