Question

I plan to run a WCF-Console-Application as a Windows service. The WCF-Console-Application calls a FoxPro-DLL to access data in FoxPro-DBF (read and write). Different clients (WPF-Application) should consume the WCF-Service of the Console-Application to display and edit the data from the FoxPro-DBF.

If only one Client at a time calls the WCF-Console-Application, everything works fine. But the WCF-Console-Application does not handle parallel calls from multiple clients correctly.

The WCF-Console-Application consists of these classes:

  • main Class: derived from ServiceBase, can be either called from console or started as service

    public class Service : ServiceBase { public ServiceHost serviceHost = null;

        const string CONSOLE = "console";
    
    
        public Service()
        {
            this.ServiceName = "ServiceTest";
        }
    
    
        public static void Main(string[] args)
        {
             if (args.Length == 1 && args[0].Equals(CONSOLE))
            {
                new Service().startConsole();
            }
            else
            {
                ServiceBase.Run(new Service());
            }
        }
    
        private void startConsole()
        {
            Console.WriteLine(string.Format("{0}::start Service...", GetType().FullName));
            OnStart(null);
    
            Console.WriteLine(string.Format("{0}::ready (ENTER to stop)", GetType().FullName));
            Console.ReadLine();                                                                 
    
            OnStop();                                                                           
    
            Console.WriteLine(string.Format("{0}::stop Service", GetType().FullName));              
    
        }
    
        protected override void OnStop()
        {
            if (this.serviceHost != null)
            {
                this.serviceHost.Close();                                    
                this.serviceHost = null;
            }
        }
    
        protected override void OnStart(string[] args)
        {
            if (this.serviceHost != null)
            {
                this.serviceHost.Close();                                    
            }
    
    
           this.serviceHost = new ServiceHost(typeof(Server.TestServer));              
           this.serviceHost.Open();                                         
    
    }
    
  • ServiceInstaller: installs the Service, derived from Installer

[RunInstaller(true)]
public class InstallService : Installer
{
    public InstallService()
    {
        process = new ServiceProcessInstaller();                    
        process.Account = ServiceAccount.LocalSystem;                             
        service= new ServiceInstaller();                                                
        service.ServiceName = "ServiceTest";                            
        service.Description = "ServiceTest";                    
        service.DisplayName = "ServiceTest";                    
        service.StartType = ServiceStartMode.Automatic;                       

        Installers.Add(process);                                    
        Installers.Add(service);                                     
    }
}
  • static class for accessing the FoxPro-DLL
public static class DataAccess
{
    public static foxprotest.foxprotest accessData = new foxprotest.foxprotest();
}
  • ITestServer, Interface with the ServiceContract
[ServiceContract(Namespace = "http:/localhost.TestServer", SessionMode = SessionMode.Allowed)]
public interface ITestServer
{
    [OperationContract]
    String loadData(int id);
}
  • TestServer, implementation of ITestServer
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class TestServer : ITestServer
{
    public String loadData(int id)
    {
                    //set table 
        DataAccess.accessData.CTABLE = "patient";
                    //set ID 
          DataAccess.accessData.NPATIENT = id;
        //fetch the data
        DataAccess.accessData.FetchData();
                    //return data as XML
         return  DataAccess.accessData.CRESULT;
    }
}

This is how the App.Config looks like: the binding is set to netTcpBinding

  <system.serviceModel>
    <services>
      <service name="Server.TestServer" behaviorConfiguration="MyFileServiceBehavior">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8888/"/>
            <add baseAddress="net.tcp://localhost:52/"/>
          </baseAddresses>
        </host>
        <endpoint address="TestServer" binding="basicHttpBinding"
          name="b" contract="Server.ITestServer" />
        <endpoint address="TestServer" binding="netTcpBinding"
          name="c" contract="Server.ITestServer" />
      </service>
    </services>
<behaviors>
  <serviceBehaviors>
    <behavior name="MyFileServiceBehavior">
      <serviceMetadata httpGetEnabled="true" />
      <serviceThrottling maxConcurrentCalls="80" maxConcurrentSessions="80"
          maxConcurrentInstances="80" />
    </behavior>
  </serviceBehaviors>
</behaviors>
</system.serviceModel>

I used svcutil.exe to generate the output.config and TestServer.cs for the WPF-Application.

The FoxPro-DLL is built as multi-threaded COM server, it is registered correctly and VFP9T.DLL is used. There is a little delay in the function loadData(), to test the parallel calls. When I call the DLL from multiple FoxPro-Instances, everything works as expected. If I include the dll into the WPF-Application and call it from there, it also works. Only with multiple calls through the WCF-Console-Application, it does not work correctly.

When I run the WCF-Console-Application through console and make a Console.WriteLine before every line in TestServer.LoadData(), the second call keeps hangig in front of

DataAccess.accessData.CTABLE = "patient";

until the first call is finished. The worst thing is: the returned XML-string is the same when I make parallel calls. Immediately after the first call with an ID, i start a second call with a different ID. For both calls I get the XML-String with the second ID.

What can I change, to get the parallel call to the FoxPro-DLL inside the WCF-Console-Application working? I tried every combination of InstanceContextMode and ConcurrencyMode, without success. Do I need thread safety? If so, what do I have to change? The use of ODBC or a SQL-Server are no option for this project.

Thanks for any suggestion and advice!

EDIT: If I restart the WCF-Console-Application, only the first parallel test delivers wrong XML-strings. If I do a second parallel call, the returned XML-strings are correct. But the problem with the parallel call still exists.

Était-ce utile?

La solution

BasicHttpBinding does not support sessions. You need to use InstanceContextMode.PerCall to make sure that every call is run in its own thread.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class TestServer : ITestServer
{
   ...
}

Update:

Is DataAccess.accessData static? If yes - that needs to be changed. Static data is shared across multiple threads. Therefore service can return data updated by another call if InstanceContextMode.PerCall is used. ConcurrencyMode.Single still can be used with mutable static data but you will get all scalability issues of single thread.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top