3

We have created a Service Reference in one of our projects.

Now we create a instance of this when the application starts. Also when the application stats we add a event handler for Service.Method1Completed, Service.Method2Completed ect.

Then on specific events we call Service.Method1Async, Service.Method2Async ect. note those calls are issued by different threads.

But on some computers the event handler is never fired, so we started to check for FirstChanceExceptions, as it turns out when this happens the following FirstChanceExceptions occur.

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

System.ObjectDisposedException Cannot access a disposed object. Objectname: System.Net.Sockets.NetworkStream. vid System.Net.Sockets.NetworkStream.UnsafeBeginWrite(Byte[] buffer, Int32 offset, Int32 size, AsyncCallback callback, Object state)

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

Is this a invalid way to use the Service Reference? And if it is how could i use synchronize my calls correctly while using async methods with events (note im suck on .net 4.0 and VS 2010, so await is out of the picture..).

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 configuration:

<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
John Saunders
  • 160,644
  • 26
  • 247
  • 397
Peter
  • 37,042
  • 39
  • 142
  • 198

2 Answers2

2

They are not thread-safe (ClientBase for example is not). But they are cheap to create and destroy. Create one per thread or even one per call. No need to synchronize.

usr
  • 168,620
  • 35
  • 240
  • 369
  • I Created a new project where i made mass calls to a webservice, with one client per request and im sorry to say im still getting `ObjectDisposedException` (they are only first Chance exceptions and i never get the Completed callback...) – Peter Sep 15 '14 at 14:54
  • OK, that code looks fine. Threading is a risk area but I see no shared state except for the count variable. Make your code single-threaded for testing purposes. Does this specific problem still happen? – usr Sep 16 '14 at 08:30
  • I just tried to change the code to only spin up one thread in MultipleClientInstances and then i increased the number of calls from 400 to 4000 and i still get errors.... – Peter Sep 16 '14 at 10:07
  • Then it's not a threading issue. Call the service over HTTP and use Fiddler to see HTTP and network-level errors. What errors do you find? If any, post meaningful screenshots of them. – usr Sep 16 '14 at 10:40
  • when i run the client against IIS this error stops to occur but i still find it unsettling that some error on the server can cause a memory leak on the client... – Peter Sep 17 '14 at 06:16
1

ClientBase is might not be thread-safe* so derived proxies aren't either so you can't use them this way.

You could create a thread-safe base class and custom generate the proxy classes.

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

One thing to worry about is when multiple requests are sent through the same channel and the channel faults for some/any reason.

*Have a look at this question too: Is WCF ClientBase thread safe?

Community
  • 1
  • 1
Emond
  • 50,210
  • 11
  • 84
  • 115