Solved

Client/Server multithreading

Posted on 2002-03-31
20
474 Views
Last Modified: 2011-10-03
Hi.

I'm trying to implement some simple app which manages a list of user, and calling them, using call-back functions.

The idea is simple:
  There is a server which have an interface on him. Between the client and the server, there is another tier. This tier is the login server, which implements the interface, and Logs-in the user.

The problem begins when I'm using the call-back functions of the interface, for calling the user (client).

If the user decides to pop-up a msgbox, my server gets stuck.

I think the solution is multithreading, but I have no idea, how this can be done.

I'd appriciate help with or w/o source code.

Regards,
Ophir.
0
Comment
Question by:ophirg
  • 6
  • 5
  • 4
  • +3
20 Comments
 
LVL 17

Expert Comment

by:inthedark
ID: 6908685
Instead of using callbacks you could use another method.  To get round this problem I created a simple class which allows connection of a server/control process using Winsock. Each task that starts connects and registers with the server task. So the the server can send responses to clients which Windows delivers when it gets a round to it.  The problem is that when you send a message to client or server you have to monitor the time that the request was sent handle a possible time-out.

So in my code I can say like:

Declarations:

Dim WithEvents MyClient as ucWorld

Perhaps form/app load:

Set MyClient = New ucWorld

ok = MyClient.ClientConnectOK("localhost", 1234)

So now bang a message to the server:

Message = "LOGIN|MYNAME|PASSWORD"
ok=MyClient.ClientSendOK(Message)

The reponse from the serve then arrices in the ecent defined by the class ucWorld.

Example:

Private Sub MyClient_MessageToClient(MessageReceived As String, MessageToSend As String)

msgbox "The server send this message: " + MessageReceived

MessageToSend = Inputbox("Please type a response")

End Sub


The server side is simple in you form/app load:

Set MyServer = New VIPWorld.ucWorld
ok = MyServer.ServerStartOK(Val(txtPort), txtIP) ' port/ip address
If ok Then
    List1.Clear
    AddMessage "Server: Started"
Else
    AddMessage "Server: Failed to Start: " + MyServer.ErrorMessage
End If

To broadcast to all clients:

MyServer.BoardCast "Send this message"

Handle data from a client via an event generated in MyServer:

Private Sub MyServer_MessageToServer(Client As VIPWorld.ucWorld, ByVal NewClient As Boolean, MessageReceived As String, MessageToReply As String)

' This event is fired when ever the server receives a message

If NewClient Then
    AddMessage "Server: New Client ID: " + CStr(Client.MyID) + " " + MessageReceived
    MessageToReply = "OK New Client"
Else
    AddMessage "Server: Old Client ID: " + CStr(Client.MyID) + " " + MessageReceived
    MessageToReply = "OK"
End If

End Sub

But the secret is to make sure the server is a run unattended ActiveX service which won't talk directly to the outside world.
0
 
LVL 18

Expert Comment

by:mdougan
ID: 6910379
If you're working in VB6 then you don't really have a multi-threading option.  Under VB5 you could call the CreateThread API and callbacks would work fine.  But, under VB6, you can compile code using CreateThread, but it will crash all over the place.  VB.Net is supposed to have a lot more support for true multi-threading, so, that might also be an option for you.

Oh, and one other thing you might try, is that you can declare the instance of your login server With Events.  Then, in the login server, it can Raise an Event, which should cause the event code in your client application to execute even if the user is stuck on a modal messagebox.
0
 
LVL 18

Expert Comment

by:mdougan
ID: 6910388
Here is how the Event thing works.  In your login server, declare the event you want to raise:

Public Event PercentDone(ByVal lCurrent As Long, ByVal lTotal As Long, ByVal bShowCount As Boolean, ByRef Cancel As Boolean)


In the code of the class of your login server component, when you want to alert the client, then Raise the Error:

RaiseEvent PercentDone(rwIndex, CLng(grd.Rows - 1), True, blnCancel)

Then, in your client application declare your object variable for the login server component "With Events"

Private WithEvents oUpdate  As CLoanUpdate

Then, you will see events listed for your object variable, and just fill in the code that you want to execute when the login server component raises the event.

Private Sub oUpdate_PercentDone(ByVal lCurrent As Long, ByVal lTotal As Long, ByVal bShowCount As Boolean, Cancel As Boolean)
Dim Percent As Single

    If lTotal > 0 Then
        Percent = (lCurrent / lTotal) * 100
    Else
        Percent = 0
    End If
    If bShowCount Then
        StatusBar1.Panels(2).Text = lCurrent & " of " & lTotal
    Else
        StatusBar1.Panels(2).Text = Percent & "% Complete"
    End If
    pbrFeedback.Value = Percent

End Sub

0
 
LVL 17

Expert Comment

by:inthedark
ID: 6912598
ophirg, I have run some performance timings. Using Winsock to send messages between a server process and a client process it is possible to send upto 5000 messages per second and if the client processes is running on different pc's via a LAN it can achieve 1500 messages per second.

But if you just want to communicate between procces within the same exe you can use the withevents as suggested by mdougan and exampled in my previous post - but the server process will still freeze when any sub displays a modal form or blocks the process with a loop without any DoEvents.

But I think there is a better solution.  I have seen a different MessageBox with allows events to be handled while the messagebox is on screen.  You could build your own messagebox form and do a global replace changing the name MsgBox to your own function name.  Display the message in a non-modal method and use a timer control and a global indicator to see which response the operator choose.

But if you want genuine multithreaded processing as mdougan proposes VB6 may not be the tool to use.  But using Winsock you can create safe multitasking,  Initiate each client as a seperate EXE and communicate/send data between the tasks using a via simple class which handles the messaging for you.

If you wish I can post a copy of the class here.

0
 
LVL 1

Author Comment

by:ophirg
ID: 6917277
Hello inthedark,

First of all, building a new MessageBox, is out of the question. The reason is simple:
Let's say, another programmer, comes tomorrow to replace me. Why should he be aware of this restriction. It doesn't sound very logical that if some other programmer raises a MsgBox, the whole server gets stuck.

Thanks anyway,
Ophir.




0
 
LVL 1

Author Comment

by:ophirg
ID: 6917282
Hi all!

I refuse to believe that there isn't any nice and easy  solution to this simple client/server problem.
This problem must be the first problem in any good book about client/server. It should be referred as "How not stucking your server".

I think I'll ask god as next thing, but for now, I'll appreciate any other help.

Thank you very much for your efforts,
Ophir.



0
 
LVL 18

Expert Comment

by:mdougan
ID: 6917310
Your solution is not that simple, because you are trying to deal with the possibility that the user is currently pre-occupied with a Modal messagebox window.  See, client applications are designed with the thought that no matter what the system can do, the user can only do pretty much one thing at a time.  So, if there is a Modal window showing that is part of your application, you are hosed.  There is not much you can do, to get your application to communicate with the user, because they are busy with the modal window.

Now, the event procedures that I gave you will allow the client application to execute some code in the background, even though there is a modal messagebox on the screen.  Have you tried this code?  If so, what problems, if any, have you had with it.  If you want a resolution for your problem, you have to provide feedback to the people who take the time to try to help you solve your problem.  Otherwise, you are one your own  ;)
0
 
LVL 1

Author Comment

by:ophirg
ID: 6917316
Hi, mdougan!

In your code, I don't see any reference to my MsgBox problem, bcause you don't raise any in the client's code.

Referring to your last comment, I don't want the client to do anything when the MsgBox raises. I just want my server not to get stuck. The server, can't move on, unless the client releases the MsgBox. This situation cannot happen and this I want to prevent.

Thanks (hoping I won't be left on my own),
Ophir.

0
 
LVL 17

Expert Comment

by:inthedark
ID: 6917513
Take a look at this. Read all of th comments that have been placed.

http://www.planet-source-code.com/vb/scripts/ShowCode.asp?lngWId=1&txtCodeId=26900

I have seen a better version of MSGBOX I think that if you call the API version of MessageBox it takes the same params but will not halt the server.

See this link:

http://www.vbapi.com/ref/m/messagebox.html

0
 
LVL 17

Expert Comment

by:inthedark
ID: 6917527
You will note that Srideep Prasad's DLL has a few comments like: "Won't work with modal form."

Also I think you should not share variables with the threads.  If you do share variables set a global indicator before being allowed to change global variables:

' in a module
Global VarControl as Long


In a thread where ou need to change a global variabled, each thread must have its own ID like 1,2,3,4 etc.

Before Change of global values:


Do
   Do
      If VarControl=0 Then Exit Do
      Sleep 10
   Loop

   MyVar=MyID
   DoEvents
   If MyVar=MyID Then Exit Do
   Sleep 50
Loop

' change as required


VarControl=0 'allow other threads access to globals







 
0
Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

 
LVL 1

Author Comment

by:ophirg
ID: 6917542
inthedark,
   I've seen this sample already. But... as I said before, using a diefferent MessageBox is not the answer to my server problem.

   Imagine some sort of situation where you use the MSN interfaces in your project, and when you pop-up a MsgBox, the whole Microsoft MSN Server stucks. Doesn't make any sense.

I have to solve this.

Thanks,
Ophir.
0
 
LVL 18

Expert Comment

by:mdougan
ID: 6917831
I didn't include the messagebox in the code, because I was giving you an example of a way that your server can talk to your client machine that is independent of whether they have a messagebox on your form or not.  

If your server has to communicate to your client, it can raise events and the events should be processed by the client whether there is a messagebox on their screen or not.  
0
 
LVL 17

Expert Comment

by:inthedark
ID: 6919475
I presume that your project is just one exe.  In which case it is running as one thread.  So when any modal from is displayed, or any sub goes into loop the whole thing will stop.  VB provides the illusion of multi-tasking. But when any timer or sub or event fails to yield with a doevents or sleep the whole thing stops.

The only way to stop this is to create each client as a separate thread.

Or you could do it the simple way and create each client as a separate exe as each exe starts it contacts a server exe which controls the client processes.  All this can be done with just a few lines of code.  The client processes can run just like a thread in a hidden context.

The only other choice you have is to write a different version of MessageBox or maybe try the API MessageBox - although for some reason you don't want to see this as a solution but it is the price you have to pay for creating the illusion of making your vb app multitasking.

So here is a summary of your choices:

1) Run each client as a seperate thread.
2) Run each client as a seperate exe.
3) Don't use modal forms and ensure all loops have dovents.
0
 
LVL 3

Expert Comment

by:adg
ID: 6919943
VB6 does offer true multithreading using an ActiveX exe. It is very possible in a single process (with multiple threads) to pop up a msgbox and have the other threads continue to function.  

I'm not sure if that is the question.  Would that help?

0
 
LVL 3

Expert Comment

by:adg
ID: 6920077
OK, I'm an idiot.  I just tried it, and when the msg box popped on the second thread the first thread stopped too.  I know they were different threads because I displayed APP.ThreadId.  

Next time I will attempt to verify my comments before posting rather than after.

0
 
LVL 1

Expert Comment

by:adopo
ID: 6921326
At the risk of making myself look more stupid, I have an idea that might work.  I want to decouple the call from the main thread to the workerthread via a timer. I think that might keep the main thread from locking across the worker thread.  I have to go to work now but I'll post the results (either way) in about 12 hours.  
0
 
LVL 3

Expert Comment

by:adg
ID: 6922324
OK, it works but it is kind of complicated.  I'm not saying this is the best way but it does show that multi-threading in VB is possible with a modal form.  If you want to try it then create an activex exe project containing the following.  Make sure to set the threading model and start mode as shown in MainModule.bas.


========================================================
Option Explicit

' Module MainModule.bas
' ActiveX EXE Name = MTP
' Startup Object = Sub Main
' Threading Model = Thread per Object
' Start Mode = Standalone
 
Private MainThreadForm As MainThreadForm
' checks to see if first time thru Sub Main
Private ThreadCheck As ThreadCheck
Public WorkerThread As ModalObject

Sub Main()
    Set ThreadCheck = New ThreadCheck
    If ThreadCheck.FirstTime Then 'create the second thread
        Set MainThreadForm = New MainThreadForm
    End If
    Set ThreadCheck = Nothing  
End Sub

Public Sub CreateThread()
    Set WorkerThread = CreateObject("MTP.ModalObject")
End Sub
========================================================
Option Explicit

' MainThreadForm.frm.

' This form provides two timers

' Timer1 is used by the MainModule to create the modal object.  It introdues a delay
' that allows the first thread to complete initialization before it tries to
' create additional threads.

' Timer2 is used to redisplay the time every 1/2 second.  This shows
' that the mainthread is still processing even while the worker thread
' is displaying a modal msg box.


Private Sub Command1_Click()
    WorkerThread.InitModalMsg
End Sub

Private Sub Form_Initialize()
    Label1.Caption = "Thread=" + Hex$(App.ThreadID)
    With Timer1
        .Interval = 100
        .Enabled = True
    End With
    With Timer2
        .Interval = 500
        .Enabled = True
    End With
    Me.Show
End Sub

Private Sub Timer1_Timer()
    Timer1.Enabled = False
    CreateThread ' defined in the main module
End Sub

Private Sub Timer2_Timer()
    With Label2
        .Caption = CStr(Now)
        .Refresh
    End With
End Sub
==========================================================
Option Explicit

Private CallRef As Object

' Timerform.frm
' This form is created by the WorkerThread for the sole purpose
' of supporting a timer.  There are much better ways to do
' interval timing in VB but this is just a proof-of--concept
' that multi-threading works in VB.

Public Sub SetCallRef(NewValue As Object)
    Set CallRef = NewValue
    With Timer1
        .Interval = 50
        .Enabled = True
    End With
End Sub

Private Sub Form_Load()

End Sub

Private Sub Timer1_Timer()
    Timer1.Enabled = False
    CallRef.DisplayModalMsg
End Sub
============================================================
Option Explicit

' ThreadCheckForm.frm
' This form is a place holder used by ThreadCheck.cls
' to decide if we are in the first thread or not

Private Sub Form_Load()

End Sub
============================================================
Option Explicit

' Class ThreadCheck.cls
' Instancing Private

' this object runs on the UI (Main) thread

' The FirstTime property will be True on the main (first) thread of this process
' and false on all other threads.  Since Sub Main is executed again for each thread, this
' is used to select logic that should only happen once per process.

' The caption on a hidden form is used to make the decision
Private Const PROC_CAPTION = "arf"

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private ThreadCheckForm1 As ThreadCheckForm
Private bolFirstTime As Boolean
Private Const ZERO = 0

Private Sub Class_Initialize()
    If ZERO = FindWindow(vbNullString, PROC_CAPTION) Then
        bolFirstTime = True
        Set ThreadCheckForm1 = New ThreadCheckForm
        Load ThreadCheckForm1
        ThreadCheckForm1.Caption = PROC_CAPTION
    Else
        bolFirstTime = False
    End If
End Sub

Private Sub Class_Terminate()
'    If bolFirstTime Then
'        Unload ThreadCheckForm1
'        Set ThreadCheckForm1 = Nothing
'    End If
End Sub

Public Property Get FirstTime() As Boolean
    FirstTime = bolFirstTime
End Property
============================================================
Option Explicit

' Class ModalObject.cls
' Instancing = MultiUse


Private TimerForm1 As TimerForm

Public Sub InitModalMsg()
    Set TimerForm1 = New TimerForm
    TimerForm1.SetCallRef Me
End Sub

Public Sub DisplayModalMsg()
    MsgBox "Modal Thread=" + Hex(App.ThreadID)
    Unload TimerForm1
    Set TimerForm1 = Nothing
End Sub
============================================================
0
 
LVL 1

Author Comment

by:ophirg
ID: 6924701
Hello all!

First of all, thanks for all your efforts.
Second, I think I solved the problem by myself, but there are some mines in my way, which I'm about to remove.

For those of you who didn't understand completely my question, here is the problem in the simplest way:

Try showing to Message-Boxes (MsgBox and not API  MessageBox()) at a time. One at the server (ActiveX EXE and the other at the client)

One last thing. I do want to give my points away for your efforts, so anyone who can write a robust code which includes a raised event in a multithread (My original question) will get the points.

Thank you all very much for your efforts,
Ophir.
0
 
LVL 1

Accepted Solution

by:
ariell earned 300 total points
ID: 7167667
Hi there,
Your problem stemming from the fact that all the client objects r created in the same thread, so if one of the objects popup a message box it blocking the other objects from being activate until the object with the popup release the server.
I don't find any problem to build this kind of client/server app.
what u need is some kind of multithread app like ActiveX EXE that vb supply.
This ActiveX EXE Server simply create each object (that client request) in a single thread (STA).
It depend how u configure the EXE Server and the object instance property.
Shortly, mark the option Thread per Object in the Server App properties, and set the instance property of the object to either MultiUse or SingleUse.
the difference between the two is that in Single use the server create a new process for each object, and in MultiUse the server create a single STA for each object.
The following code illustrate a simple Client/Server app.
Run each of the client in it's own process, but first, click on the button in Client1 to popup a message in the server, leave the messagebox as it is (don't close it)
and then click the button in Client2 to see how the server trigger the event in Client2 although the messagebox is at the server.

Server code:
Option Explicit

Public Event EventTriggered()

Public Sub PopUpMessageBoxAtServer()
  MsgBox "Message in server process"
End Sub

Public Sub TriggerEvent()
  RaiseEvent EventTriggered
End Sub

Client1 code:
-------------------------------------------------
Option Explicit

Dim WithEvents o As ExeServerTest.Widget

Private Sub Command1_Click()
  o.PopUpMessageBoxAtServer
End Sub

Private Sub Form_Load()
  Set o = New ExeServerTest.Widget
End Sub
-------------------------------------------------

Client2 code:
-------------------------------------------------
Option Explicit

Dim WithEvents o As ExeServerTest.Widget

Private Sub Command1_Click()
  o.TriggerEvent
End Sub

Private Sub Form_Load()
  Set o = New ExeServerTest.Widget
End Sub

Private Sub o_EventTriggered()
  MsgBox "This is a callback from the server"
End Sub
-------------------------------------------------


0
 
LVL 1

Author Comment

by:ophirg
ID: 7167673
Hi, ariell.

   A great answer indeed. Simple. Work. Great.

Thanks,
Ophir.
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Suggested Solutions

The debugging module of the VB 6 IDE can be accessed by way of the Debug menu item. That menu item can normally be found in the IDE's main menu line as shown in this picture.   There is also a companion Debug Toolbar that looks like the followin…
Since upgrading to Office 2013 or higher installing the Smart Indenter addin will fail. This article will explain how to install it so it will work regardless of the Office version installed.
Get people started with the process of using Access VBA to control Excel using automation, Microsoft Access can control other applications. An example is the ability to programmatically talk to Excel. Using automation, an Access application can laun…
This lesson covers basic error handling code in Microsoft Excel using VBA. This is the first lesson in a 3-part series that uses code to loop through an Excel spreadsheet in VBA and then fix errors, taking advantage of error handling code. This l…

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now