Ensuring Asynchronous Tasks run (or fail) in the Correct Sequence

robertb_NZ

Well-known member
Joined
May 11, 2010
Messages
146
Location
Auckland, New Zealand
Programming Experience
10+
I have been using this code in synchronous mode.
    Dim UploadInvoked As Boolean = False    '<==   global: defined at the top of the Class
    Sub UploadCPYFile(CPYLIBNAME As String)
        '   Designed for uploading SQL and Message (web service) definitions to CPYLIB.  Build 13.3, jobs SQLDEF and JazzFTP
        '       Set up for multithreading in case I want to do this in the future, but currently used synchronously
        Dim DoUpload As New Threading.Thread(AddressOf UploadCPY)
        UploadCPY(CPYLIBNAME)   '   Run synchronously
        'DoUpload.Start(CPYLIBNAME)  '   Run asynchronously
    End Sub
    Sub UploadCPY(CPYLIBNAME As String)
        If UploadInvoked Then
            Exit Sub
        End If
        UploadInvoked = True
        '   Invoke JazzFTP.Upload asynchronously.  
        Dim JazzFTP As New JazzFTP(AddressOf Message, AddressOf IsFinished)
        JazzFTP.UploadCpyDef(CPYLIBNAME)
        UploadInvoked = False
    End Sub

By swapping the commenting to
'UploadCPY(CPYLIBNAME) ' Run synchronously
DoUpload.Start(CPYLIBNAME) ' Run asynchronously
it runs asynchronously, apparently without problem.

However it is invoked twice, and it is important that
UploadCPYFile(Name1)
runs (or fails) before
UploadCPYFile(Name2)

If the prior thread is still active then I would like the 2nd thread to wait for the first to complete or fail, although I don't want the user to have to wait.

Does this asynchronous code guarantee this? I suspect not. Looking at the code I think that if UploadCPYFile(Name2) is invoked before UploadCPYFile(Name1) has finished then the 2nd thread is simply abandoned, terminating immediately when UploadInvoked is tested at the beginning of UploadCPY. I feel that, instead of using a simple global Boolean defined at the top of the class I should possibly use a stack variable, or code a wait.

Can somebody please tell me whether my suspicion is correct and so this code will not work as I want? If the code is incorrect, can you point me to an example of the sort of code that I should have?

Thank you, Robert.
 
At the beginning of the second thread, call Thread.Join to make it wait on the first thread without halting the UI thread.

Thank you jmcilhinney, this looks good. But I'm afraid that this dumb kiwi still needs a little help. If I understand what you're suggesting I can write: -
VB.NET:
    Sub UploadCPYFile(CPYLIBNAME As String)
        '   Designed for uploading SQL and Message (web service) definitions to CPYLIB.  Build 13.3, jobs SQLDEF and JazzFTP
        '       Set up for multithreading in case I want to do this in the future, but currently used synchronously
        Dim DoUpload As New Threading.Thread(AddressOf UploadCPY)
        'UploadCPY(CPYLIBNAME)   '   Run synchronously
        DoUpload.Start(CPYLIBNAME)  '   Run asynchronously
    End Sub
    Sub UploadCPY(CPYLIBNAME As String)
 [B]       DoUpload.join  [/B]
        If UploadInvoked Then
            Exit Sub
        End If
        UploadInvoked = True
        '   Invoke JazzFTP.Upload asynchronously.  
        Dim JazzFTP As New JazzFTP(AddressOf Message, AddressOf IsFinished)
        JazzFTP.UploadCpyDef(CPYLIBNAME)
        UploadInvoked = False
    End Sub
But I read in https://msdn.microsoft.com/en-us/library/95hbf2ta(v=vs.110).aspx
You should never call the Join method of the Thread object that represents the current thread from the current thread. This causes your app to hang because the current thread waits upon itself indefinitely,​

Won't this be a problem here?
Regards, Robert
 
I wasn't suggesting joining with the current thread. If you start Thread1 and Thread2 then calling Join on Thread1 in Thread2 will ensure that Thread1 terminates before Thread2 continues.
 
I wasn't suggesting joining with the current thread. If you start Thread1 and Thread2 then calling Join on Thread1 in Thread2 will ensure that Thread1 terminates before Thread2 continues.

I get the way that the example in https://msdn.microsoft.com/en-us/library/95hbf2ta(v=vs.110).aspx works. A key feature of that example is that ThreadProc is started twice with separate Thread.Start statements, with different names: -
Module Example
Dim mainThread, thread1, thread2 As Thread

Public Sub Main()
mainThread = Thread.CurrentThread
thread1 = new Thread(AddressOf ThreadProc)
thread1.Name = "Thread1"
thread1.Start()

thread2 = New Thread(AddressOf ThreadProc)
thread2.Name = "Thread2"
thread2.Start()​
End Sub
SNIP
End Module​

But in my situation I don't have the luxury of two separate thread.start statements. UploadCPYFile is a Sub that is called, currently twice, from somewhere else in the class. It runs as part of the main thread that controls the UI. If I write the thread.join statement at #1, then we correctly start the first UploadCPY asynchronously, but the second will halt the UI until the first UploadCPY is finished.
Sub UploadCPYFile(CPYLIBNAME As String)
Dim DoUpload As New Threading.Thread(AddressOf UploadCPY)
#1 DoUpload.Join
DoUpload.Start(CPYLIBNAME) ' Run asynchronously
End Sub
Sub UploadCPY(CPYLIBNAME As String)​
SNIP
End Sub​

As we've agreed, if I write DoUpload.join within UploadCPY, then I'd be joining the task to itself. If I give the thread an explicit name then I have to use logic that will give a different name to the 2nd thread.

Perhaps this is the right answer: -
Dim UploadInvoked As Boolean = False #2

Sub UploadCPYFile(CPYLIBNAME As String)
If UploadInvoked Then #3
Dim DoUpload2 As New Threading.Thread(AddressOf UploadCPY)
DoUpload.Name = "DoUpload2"
DoUpload2.Start(CPYLIBNAME) ' Run asynchronously
Else
Dim DoUpload1 As New Threading.Thread(AddressOf UploadCPY)
DoUpload1.Name = "DoUpload1"
DoUpload1.Start
End If
End Sub

Sub UploadCPY(CPYLIBNAME As String)
If (Thread.CurrentThread.Name = "DoUpload1" And #4
DoUpload2.ThreadState <> ThreadState.Unstarted)
DoUpload2.Join()
End If

If UploadInvoked Then #5
Exit Sub
End If
UploadInvoked = True
' Invoke JazzFTP.Upload asynchronously.
Dim JazzFTP As New JazzFTP(AddressOf Message, AddressOf IsFinished)
JazzFTP.UploadCpyDef(CPYLIBNAME)
UploadInvoked = False​
End Sub​

Notes
#2 This Dim is currently in the class's global variables. The code to test for it at the beginning of UploadCPY is already in the program
#3 This code is intended to ensure that a 2nd (or nth) thread has a different name to the current thread
#4 This is adapted from the MSDN example, substituting my variable names
#5 With the statement DoUpate2.Join() I believe that this IF/End If is now superfluous, as it can never be true. If one DoUpload overruns another, then DoUpload2.Join() will cause the 2nd to wait until the first has finished, which will set UploadInvoked to false.

jmcilhinney, can you please confirm for me that my following understanding is correct: -
1. This code is correct, at least provided that I can guarantee that there never more than 2 calls to UploadCPYFile
2. The IF/End of #5 can be removed

And can you tell me whether this would work as a truly general solution, with N calls to UploadCPYFile, or whether I'd then need more sophisticated code in UploadCPYFile?

Thank you for your help, Robert.
 
It sounds as what you want is a Queue(Of String) and a single worker thread that process it.
 
Back
Top