Are visual studio generated WebService clients thread safe?

We have created a service link in one of our projects.

We now instantiate this when the application starts. Also, when application statistics add an event handler for Service.Method1Completed

, Service.Method2Completed

ect.

Then on specific events we call Service.Method1Async

, Service.Method2Async

ect. note that these calls are issued by different threads.

But on some computers, the event handler never runs, so we started checking FirstChanceExceptions

how it turns out when this happens the following happens FirstChanceExceptions

.

System.Net.Sockets.SocketException Invalid argument was supplied.
vid System.Net.Sockets.Socket.SetSocketOption (SocketOptionLevel optionLevel, SocketOptionName optionName, Int32 optionValue, Boolean silence)

System.ObjectDisposedException Unable to access the remote object. Object name: System.Net.Sockets.NetworkStream. VID System.Net.Sockets.NetworkStream.UnsafeBeginWrite (Byte [] buffer, Int32 offset, Int32 size, AsyncCallback, object state)

System.Net.WebException The request was canceled.
vid System.Net.HttpWebRequest.EndGetResponse (IAsyncResult asyncResult)

Is this the wrong way to use the Service Reference? And if so, how can I properly synchronize my calls when using async methods with events (note that the suck is on .net 4.0 and VS 2010, therefore await

missing.).

ServerCode:

<ServiceContract()>
Public Interface IService1

    <OperationContract()>
    Function GetData(ByVal value As Integer) As String

End Interface

<ServiceBehavior(ConcurrencyMode:=ConcurrencyMode.Multiple, InstanceContextMode:=InstanceContextMode.Single, UseSynchronizationContext:=False)>
Public Class Service1
    Implements IService1

    Public Sub New()
    End Sub

    Public Function GetData(ByVal value As Integer) As String Implements IService1.GetData
        Return String.Format("You entered: {0}", value)
    End Function

End Class

      

Server config:

<system.serviceModel>
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
        <serviceMetadata httpGetEnabled="true"/>
        <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
        <serviceDebug includeExceptionDetailInFaults="false"/>
        <serviceThrottling maxConcurrentCalls="1000" maxConcurrentSessions="1000" maxConcurrentInstances="1000" />
      </behavior>
    </serviceBehaviors>
  </behaviors>
  <bindings>
    <customBinding>
      <binding>
        <binaryMessageEncoding>
          <readerQuotas maxArrayLength="5242880" />
        </binaryMessageEncoding>
        <httpTransport maxBufferPoolSize="52428800" maxReceivedMessageSize="5242880" maxBufferSize="5242880" authenticationScheme="Anonymous" />
      </binding>
    </customBinding>
  </bindings>
  <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
</system.serviceModel>

      

Client code:

Imports System.Threading
Imports System.IO

Module Module1
    Dim errorQueue As New System.Collections.Concurrent.ConcurrentBag(Of String)
    Dim count As Integer

    Sub Main()
        AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf CurrentDomain_UnhandledException
        AddHandler AppDomain.CurrentDomain.FirstChanceException, AddressOf AppDomain_FirstChanceException

        MultipleClientInstances()
        Console.WriteLine("Calls are in progress, press any key when they are done!")
        Console.ReadKey()

        Thread.MemoryBarrier()
        errorQueue.Add("Number of requests remaining " + count.ToString())
        Dim line As String = ""
        Using writer As New StreamWriter("output.log")
            While (errorQueue.TryTake(line))
                writer.WriteLine(line)
            End While
        End Using
    End Sub

    Private Function GetClient() As ServiceReference1.Service1Client
        Dim client As New ServiceReference1.Service1Client()
        AddHandler client.GetDataCompleted, AddressOf client_GetDataCompleted
        client.Open()
        Return client
    End Function

    Private Sub MultipleClientInstances()
        Console.WriteLine("Making calls!")
        For i As Integer = 0 To 10
            Dim t As New Thread(AddressOf MakeCallsWithNewClients)
            t.Start()
        Next
    End Sub

    Private Sub MakeCallsWithNewClients()
        For i As Integer = 0 To 400
            Interlocked.Increment(count)
            Dim client As ServiceReference1.Service1Client = GetClient()
            client.GetDataAsync(i, True)

            While (Thread.VolatileRead(count) > 20)
                Thread.Sleep(5)
            End While
        Next
    End Sub

    Private Sub client_GetDataCompleted(sender As Object, e As ServiceReference1.GetDataCompletedEventArgs)
        Dim value As Integer = Interlocked.Decrement(count)
        Console.WriteLine(value)

        Dim client As ServiceReference1.Service1Client = CType(sender, ServiceReference1.Service1Client)
        RemoveHandler client.GetDataCompleted, AddressOf client_GetDataCompleted
        client.Close()
    End Sub

    Private Sub CurrentDomain_UnhandledException(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)
        If (e.ExceptionObject IsNot Nothing AndAlso e.ExceptionObject.GetType().IsSubclassOf(GetType(Exception))) Then
            If (e.IsTerminating) Then
                Console.WriteLine("Fatal exception occurred termination application, " + CType(e.ExceptionObject, Exception).ToString())
            Else
                Console.WriteLine("Unhandled exception occurred, " + CType(e.ExceptionObject, Exception).ToString())
            End If
        Else
            If (e.IsTerminating) Then
                Console.WriteLine("Fatal exception occurred termination application, " & e.ExceptionObject.ToString())
            Else
                Console.WriteLine("Unhandled exception occurred, " & e.ExceptionObject.ToString())
            End If
        End If
        errorQueue.Add("UnhandledException: " + e.ExceptionObject.ToString())
    End Sub

    Private Sub AppDomain_FirstChanceException(ByVal sender As Object, ByVal e As Runtime.ExceptionServices.FirstChanceExceptionEventArgs)
        Console.WriteLine("FirstChanceException: " + e.Exception.ToString())
        errorQueue.Add("FirstChanceException: " + e.Exception.ToString())
    End Sub
End Module

      

+3


source to share


2 answers


They are not thread safe ( ClientBase

for example, not). But they are cheap to create and destroy. Create one for each stream, or even one for each call. No need to sync.



+2


source


The client base cannot be thread safe *, so derived proxies are not, so you cannot use them that way.

You can create thread safe base class and custom generate proxy classes.

The parameter state

can be used to match the response with the outgoing request. You will find it in IAsyncResult.AsyncState in the callback event.



One thing to worry about is when multiple requests are sent through the same channel and the channel fails for whatever reason.

* Look at this question as well: Is WCF ClientBase thread safe?

+1


source







All Articles