locked
Run X number of manageable threads in vb.net RRS feed

  • Question

  • Hi,

    How can I make an application to start only "x" number of threads at a time?

    Any suggestions, Thanks

    Wednesday, March 23, 2011 4:58 PM

Answers

  • In this example, when you press the button, you create a task.

    If there is less than 10 running threads, a new thread is created and the task is executed. But if there is 10 running threads, the task is put in a queue to be executed when one of the 10 threads will become available.

    When a thread is done with a task, it calls a Callback method. There it check if there is any task in the queue. If there is, it start the next task, and if there is none, it dies

     

    Public Class Form1
    
      Private tasks As New Queue(Of action)
      Private RunningThread As Integer
      Private Lock As New Object
    
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim Task As New Action(AddressOf SomeWork)
        If RunningThread < 10 Then
          Task.BeginInvoke(AddressOf Callback, Nothing)
          Threading.Interlocked.Increment(RunningThread)
        Else
          SyncLock (Lock)
            tasks.Enqueue(Task)
          End SyncLock
        End If
      End Sub
    
      Private Sub SomeWork()
        Threading.Thread.Sleep(3000)
      End Sub
    
      Private Sub Callback(ByVal o As Object)
        If tasks.Count > 0 Then
          Dim Task As Action
          SyncLock (Lock)
            Task = tasks.Dequeue
          End SyncLock
          Task.BeginInvoke(AddressOf Callback, Nothing)
        Else
          Threading.Interlocked.Decrement(RunningThread)
        End If
      End Sub
    
    End Class
    
    • Proposed as answer by Mike Feng Saturday, April 2, 2011 11:48 AM
    • Marked as answer by Mike Feng Tuesday, April 5, 2011 8:57 AM
    Sunday, March 27, 2011 3:24 AM
  • So my question was where I do this manageability to keep count of threads running and assigning jobs to them as they get free...
    Don't remove the worker from the list in the Completed event if there are tasks waiting.  Use RunWorkerAsync to run the worker again with a new task.  There are examples here http://social.msdn.microsoft.com/Forums/en/netfxbcl/thread/d4b3b82f-d124-4c42-b22f-3d4c8669efe8
    • Proposed as answer by Mike Feng Saturday, April 2, 2011 11:48 AM
    • Marked as answer by Mike Feng Tuesday, April 5, 2011 8:57 AM
    Sunday, March 27, 2011 3:29 AM

All replies

  • You start them, why don't you count them?
    Wednesday, March 23, 2011 5:08 PM
  • Where you increment and decrement your count ?

    Wednesday, March 23, 2011 5:50 PM
  • I use BackgroundWorkers.  I keep a list of them.  Add to the list when I run one and remove from the list when I'm finished with it.

    Imports System.ComponentModel
    Imports System.Threading
    Public Class Form1
      
    Dim BGWs As New List(Of BGW)
      
    Dim R As New Random
      
    Private Sub Button1_Click(ByVal sender As ObjectByVal e As EventArgs) Handles Button1.Click
        Button1.Enabled = 
    False
        For I As Integer = 0 To 9
          BGWs.Add(
    New BGW)
          
    AddHandler BGWs(I).RunWorkerCompleted, AddressOf BGW_Complete
          BGWs(I).RunWorkerAsync(R.Next(100, 10000))
          TextBox1.Text = BGWs.Count.ToString
          TextBox1.Refresh()
        
    Next
      End Sub
      Class BGW
        
    Inherits BackgroundWorker
        
    Protected Overrides Sub OnDoWork(ByVal e As DoWorkEventArgs)
          
    MyBase.OnDoWork(e)
          
    'Do task
          Thread.Sleep(DirectCast(e.Argument, Integer))
        
    End Sub
      End Class
      Sub BGW_Complete(ByVal sender As ObjectByVal e As RunWorkerCompletedEventArgs)
        BGWs.Remove(
    DirectCast(sender, BGW))
        
    If BGWs.Count = 0 Then Button1.Enabled = True
        TextBox1.Text = BGWs.Count.ToString
      
    End Sub
    End
     Class

    Wednesday, March 23, 2011 6:26 PM
  • What about if its on going process where we have to keep inserting and removing from the list meaning its not one time job?
    Wednesday, March 23, 2011 7:11 PM
  • What about if its on going process where we have to keep inserting and removing from the list meaning its not one time job?

    What about it?  What made you assume that it was a one time job?  You add to list when you start a thread and you remove from the list when the thread is finished ad infinitum,
    Wednesday, March 23, 2011 8:01 PM
  • I guess I m not making myself clear...
    Thursday, March 24, 2011 3:18 PM
  • I guess I m not making myself clear...

    I guess not.  I don't know what else you can do with a thread other than acquire it and release it.  The count of a list of them tells you how many are running.  You limit the number running by limiting the count of the list.
    Thursday, March 24, 2011 3:47 PM
  • What I m trying to ask is that, lets say we initialized 10 threads as in your example and whenever a thread process something, it will be removed from the list till all are gone and then reenable the button to start over the count again.

    But what I was asking that if threads are started on some event and each thread (whenever starts) take 10 mins to complete, and with the time application get more events than number of threads, then we will need something to save those events and process them as threads get done.

    This will need me to keep track how many threads are waiting and running and so on so forth.

    So my question was where I do this manageability to keep count of threads running and assigning jobs to them as they get free...

    Hope I made myself clear.

     

    Sunday, March 27, 2011 12:40 AM
  • In this example, when you press the button, you create a task.

    If there is less than 10 running threads, a new thread is created and the task is executed. But if there is 10 running threads, the task is put in a queue to be executed when one of the 10 threads will become available.

    When a thread is done with a task, it calls a Callback method. There it check if there is any task in the queue. If there is, it start the next task, and if there is none, it dies

     

    Public Class Form1
    
      Private tasks As New Queue(Of action)
      Private RunningThread As Integer
      Private Lock As New Object
    
      Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim Task As New Action(AddressOf SomeWork)
        If RunningThread < 10 Then
          Task.BeginInvoke(AddressOf Callback, Nothing)
          Threading.Interlocked.Increment(RunningThread)
        Else
          SyncLock (Lock)
            tasks.Enqueue(Task)
          End SyncLock
        End If
      End Sub
    
      Private Sub SomeWork()
        Threading.Thread.Sleep(3000)
      End Sub
    
      Private Sub Callback(ByVal o As Object)
        If tasks.Count > 0 Then
          Dim Task As Action
          SyncLock (Lock)
            Task = tasks.Dequeue
          End SyncLock
          Task.BeginInvoke(AddressOf Callback, Nothing)
        Else
          Threading.Interlocked.Decrement(RunningThread)
        End If
      End Sub
    
    End Class
    
    • Proposed as answer by Mike Feng Saturday, April 2, 2011 11:48 AM
    • Marked as answer by Mike Feng Tuesday, April 5, 2011 8:57 AM
    Sunday, March 27, 2011 3:24 AM
  • So my question was where I do this manageability to keep count of threads running and assigning jobs to them as they get free...
    Don't remove the worker from the list in the Completed event if there are tasks waiting.  Use RunWorkerAsync to run the worker again with a new task.  There are examples here http://social.msdn.microsoft.com/Forums/en/netfxbcl/thread/d4b3b82f-d124-4c42-b22f-3d4c8669efe8
    • Proposed as answer by Mike Feng Saturday, April 2, 2011 11:48 AM
    • Marked as answer by Mike Feng Tuesday, April 5, 2011 8:57 AM
    Sunday, March 27, 2011 3:29 AM
  • Hi Me.Saqib,

     

    Any update?

     

    How about your program? If it works, you can share your solutions & experience here, it will be very beneficial for other community members who have similar questions. Thanks.

     

    Best regards,


    Mike Feng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Saturday, April 2, 2011 11:49 AM
  • Hi Me.Saqib,

     

    Thanks for posting in the MSDN Forum.

     

    Any update? I have marked Crazypennie and John's reply as answer, if you think it provides no help, please unmark it.

     

    Thank you for your understanding and support.

     

    Best Regards,


    Mike Feng [MSFT]
    MSDN Community Support | Feedback to us
    Get or Request Code Sample from Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    Tuesday, April 5, 2011 8:57 AM
  • This is what I did. The only question I have that If I need "Sync Lock" statements in my Increment / Decrement fucntions.


    Public Class MainForm 
       
        Private Count_LockObject As New Object
        Private CountBG As Integer = 0
        Private Limit As Integer = 2
        Private StatCounter As Integer = 15
        Dim BGWs As New List(Of BackgroundWorker)

        Public Sub New()

            ' This call is required by the Windows Form Designer.
            InitializeComponent()

            'VARIABLE TO HOLD WORKER
            Dim BGW As BackgroundWorker = Nothing

            For I As Integer = 0 To Limit - 1
                'CREATE NEW WORKER
                BGW = New BackgroundWorker
                AddHandler BGW.DoWork, AddressOf BackgroundWorker_DoWork
                AddHandler BGW.ProgressChanged, AddressOf BackgroundWorker_ProgressChanged
                AddHandler BGW.RunWorkerCompleted, AddressOf BackgroundWorker_RunWorkerCompleted
                'ADD WORKER TO LIST
                BGWs.Add(BGW)
            Next

            Timer1.Interval = 100
            Timer1.Enabled = True

        End Sub

        Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
            Timer1.Enabled = False
            Schedule()
            Timer1.Enabled = True
        End Sub

        Public Sub Schedule()

            Dim sMessage As String = ""
            Dim i As Integer = 0
           
            Do While True
                For i = 0 To Limit - 1
                    If BGWs(i).IsBusy Then

                    Else

                      ' Increment generator count now that generator is active
                      CountBG = CountBG + 1

                      With moQMessage
                            sMessage = .Body
                            BGWs(i).RunWorkerAsync(sMessage)
                      End With
                      Exit Do                  
                    End If
                Next         
            Loop

        End Sub

        'WHEN ANY EVENT OCCURS, THE SENDER WILL BE THE SPECIFIC BGW THAT FIRED THE EVENT
        Private Sub BackgroundWorker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
            Dim myBGW As BackgroundWorker = DirectCast(sender, BackgroundWorker)
            'CODE HERE
            'e.argument will be what you passed into runworkerasync
            Dim custID As Integer = CInt(e.Argument)
            e.Result = StartSQLProcessing(custID, CType(sender, BackgroundWorker), e)
            e.Result = CStr(custID) & " " & e.Result
        End Sub

        Private Sub BackgroundWorker_ProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs)
            Dim myBGW As BackgroundWorker = DirectCast(sender, BackgroundWorker)
            'CODE HERE
        End Sub

        Private Sub BackgroundWorker_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)

            Dim myBGW As BackgroundWorker = DirectCast(sender, BackgroundWorker)
            Dim str As String = ""
            'CODE HERE

            ' decrement count as this thread is done
            DecrementCount()

            If e.Cancelled Then
                AddToList("Cancelled")
            ElseIf e.Error IsNot Nothing Then
                AddToList(e.Error.ToString)
            Else
                str = e.Result & " Done at: " & Now & " Count=" & CountBG
                AddToList(str)
            End If

        End Sub

        Private Sub IncrementCount()
            SyncLock Count_LockObject
                CountBG = CountBG + 1
            End SyncLock
        End Sub

        Private Sub DecrementCount()
            SyncLock Count_LockObject
                CountBG = CountBG - 1
                StatCounter = StatCounter - 1
            End SyncLock
        End Sub

        ' Display result
        Private Sub AddToList(ByVal val As String)
            myList.Items.Add(val)    
        End Sub

        Private Function StartSQLProcessing(ByVal panelID As Integer, ByVal worker As BackgroundWorker, ByVal e As DoWorkEventArgs) As String
            ' Do SQL WORK
            Return retVal
        End Function

    End Class

    Wednesday, April 6, 2011 3:30 AM
  • Yes you do, The variable in these functions is accessed by 2 different threads

    Wednesday, April 6, 2011 9:56 AM
  • The only question I have that If I need "Sync Lock" statements in my Increment / Decrement fucntions.

    One of the main reasons for using BackgroundWorkers is to use the SynchronizationContext for synchronization.  When a worker completes its task, give it another or dispose it.  If you dispose it, remove it from the list.  The count of the list tells you how many workers you have active.

    Here's the pattern again:

    Imports System.ComponentModel
    Public Class Form1
      
    Dim BGWs As New List(Of BackgroundWorker)
      
    Dim MaxBGWs As Integer = Environment.ProcessorCount
      
    Dim Q As Queue(Of Object)
      
    Private Sub Button1_Click(ByVal sender As Object, _
                                
    ByVal e As EventArgs) Handles Button1.Click
        
    If BGWs.Count < MaxBGWs Then
          Q.Enqueue(New Object)
          
    Dim I As Integer = BGWs.Count
          BGWs.Add(
    New BackgroundWorker)
          
    AddHandler BGWs(I).RunWorkerCompleted, AddressOf Workers_Done
          BGWs(I).RunWorkerAsync(Q.Dequeue)
        
    End If
      End Sub
      Sub Workers_Done(ByVal sender As Object, _
                       
    ByVal e As RunWorkerCompletedEventArgs)
        
    Dim BGW As BackgroundWorker = DirectCast(sender, BackgroundWorker)
        
    If Q.Count > 0 Then
          BGW.RunWorkerAsync(Q.Dequeue)
        
    Else
          BGWs.Remove(BGW)
          BGW.Dispose()
        
    End If
      End Sub
    End
     Class

    Wednesday, April 6, 2011 1:56 PM
  • Need help that why my counter doesnt work properly... Please help?
     

    Public Class Form1

    Dim count As Integer = 0
    Private Count_LockObject As New Object
    ' This variable decides how many messages process at a time.
    Dim License_Number As Integer = 2
    Dim messagequeuePath As String = "myque\inbox"

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Timer1.Enabled = True
    End Sub

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
    Timer1.Enabled = False
    Dim mQue As MessageQueue = New MessageQueue(messagequeuePath)
    mQue.MessageReadPropertyFilter.SetAll()
    mQue.Formatter = New System.Messaging.ActiveXMessageFormatter

    ' Add an event handler for the ReceiveCompleted event.
    AddHandler mQue.ReceiveCompleted, AddressOf MyReceiveCompleted
    ' Begin the asynchronous receive operation.
    mQue.BeginReceive()
    End Sub

    Private Sub MyReceiveCompleted(ByVal [source] As [Object], ByVal asyncResult As ReceiveCompletedEventArgs)
    Dim mq As MessageQueue = CType([source], MessageQueue)
    Dim m As Message = mq.EndReceive(asyncResult.AsyncResult)
    Dim body, ds As String

    body = CStr(m.Body)

    SyncLock Count_LockObject
      IncrementCount()
      AddToList("Processing Start at: " & body & " " & Now.ToString & " Count=" & count)
    End SyncLock

    If count > License_Number - 1 Then
    ds = StartSQLProcessing(CInt(m.Body))
    SyncLock Count_LockObject
      ResetCount()
      AddToList("IF Processing End at: " & body & " " & ds & " " & Now.ToString & " Count=" & count)
    End SyncLock
    switchTimer(True)
    Return
    End If

    ' Restart the asynchronous Receive operation.
    mq.BeginReceive()
    ds = StartSQLProcessing(CInt(m.Body))

    SyncLock Count_LockObject
    ResetCount()
    AddToList("Processing End at: " & body & " " & ds & " " & Now.ToString & " Count=" & count)

    If count < License_Number Then
      ' Restart the asynchronous Receive operation.
      mq.BeginReceive()
    End If

    End SyncLock

    Return

    End Sub

    Public Delegate Sub switchTimerHandler(ByVal bStatus As Boolean)
    Public Sub switchTimer(ByVal bStatus As Boolean)

    If InvokeRequired Then
      Invoke(New switchTimerHandler(AddressOf switchTimer), bStatus)
      Return
    End If

    SyncLock Timer1
    If count > License_Number - 1 Then
      Return
    End If
    'count = 0
    Timer1.Enabled = bStatus
    End SyncLock

    End Sub

    Public Delegate Sub AddToListHandler(ByVal val As String)

    Private Sub AddToList(ByVal val As String)
    If InvokeRequired Then
       Invoke(New AddToListHandler(AddressOf AddToList), val)
       Return
    End If

    ListBox1.Items.Add(val)
    End Sub

    Public Delegate Sub IncrementCountHandler()
    Private Sub IncrementCount()
    'If InvokeRequired Then
    ' Invoke(New IncrementCountHandler(AddressOf IncrementCount))
    ' Return
    'End If
    SyncLock Count_LockObject
    count = count + 1
    End SyncLock
    End Sub

    Public Delegate Sub ResetCountHandler()
    Private Sub ResetCount()
    'If InvokeRequired Then
    ' Invoke(New ResetCountHandler(AddressOf ResetCount))
    ' Return
    'End If

    SyncLock Count_LockObject
    count = count - 1
    End SyncLock
    End Sub

    Public Function StartSQLProcessing(ByVal panelID As Integer) As String
    ' sql processing
    End Function
     
    End Class

    Sunday, April 17, 2011 12:59 AM
  • .NET 4.0 - TPL + System.Collections.Concurrent.
    Tom Shelton
    Sunday, April 17, 2011 1:14 AM
  • Meaning?

    Sunday, April 17, 2011 3:45 AM
  • Meaning that if you are using .net 4.0 you probably should be letting the system determine the # of threads that are appropriate for a task - and you might want to make use of the TPL to handle your threading tasks.  For instance, a simple multi-threaded for loop:

     

    Option Explicit On
    Option Strict On
    Imports System.Threading.Tasks
    
    Module Module1
    
      Sub Main()
        Parallel.For(0, 100, Sub(i As Integer) Console.WriteLine(i))
        Console.ReadLine()
      End Sub
    
    End Module
    
    

    If you seriously need to specify the # of threads, then you can - but, generally speaking to many threads is going to hurt peformance, not help.  The TPL will, by default, allocate 1 thread per processor - and the thread manager implements work stealing, so if a thread finishes it's work queue, it takes work from the queues of other threads to maximize performance.

    Currently, as of .NET 4.0, the TPL is the prefered way to do threading - you should look into it...

    The reference to the System.Collections.Concurrent was to remind people that in .NET 4.0, MS has added built in thread safe collections - you don't have to implement them yourself anymore.

     


    Tom Shelton
    Sunday, April 17, 2011 4:29 AM
  • Thanks for detailed explanation.

    But I m trying to fix the problem on the code I already have and its something (very small) that need to be fixed to make it 100% working.

    The main requirements of the app are not to process more than 2 messages at one time (meaning only run 2 threads max at given time).

    So if my count is OK, then initialization of  threads will be OK too but problem is getting my "count" variable right...

     

    Sunday, April 17, 2011 8:23 PM
  • Have you looked at System.Threading.Interlocked?  Just a quick glance and it appears your doing some unnecessary locking - a performance bottleneck by itself...  The Interlocked class can provide several Atomic operations on Integral values.

    I'm curious though, why you can just start 2 threads and let them do their work until  you turn them off?  To be honest, looking at the code above it seems that you are making things more complicated then need to be... Can you explain to me what it is that the above is trying to accomplish?


    Tom Shelton
    Monday, April 18, 2011 4:37 AM
  • just a simple console example of a producer and 2 consumers using the TPL - might show you what I mean...

     

    Option Explicit On
    Option Strict On
    Imports System.Threading
    Imports System.Threading.Tasks
    Imports System.Collections.Concurrent
    
    Module Module1
      Private q As New ConcurrentQueue(Of Message)()
      Private cancelToken As CancellationToken
    
      Sub Main()
        Dim tokenSource As New CancellationTokenSource()
        cancelToken = tokenSource.Token
    
    
        Dim tskFactory As New TaskFactory(cancelToken)
        Dim producerTask As Task = tskFactory.StartNew(AddressOf Producer, TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
        Dim consumerTasks(1) As Task
        consumerTasks(0) = tskFactory.StartNew(AddressOf Consumer, TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
        consumerTasks(1) = tskFactory.StartNew(AddressOf Consumer, TaskCreationOptions.LongRunning Or TaskCreationOptions.PreferFairness)
    
        Console.ReadKey(True)
        tokenSource.Cancel()
        Task.WaitAll(producerTask, consumerTasks(0), consumerTasks(1))
      End Sub
    
    
      Private Class Message
        Public Property Body As String = String.Empty
      End Class
    
      Private Sub Producer()
        Do
          Dim m As New Message With {.Body = DateTime.Now.ToString()}
          q.Enqueue(m)
          Thread.Sleep(50)
        Loop Until cancelToken.IsCancellationRequested
        Console.WriteLine("Producer Thread {0}: Done", Thread.CurrentThread.ManagedThreadId)
      End Sub
    
      Private Sub Consumer()
        Do
          Dim m As Message
          If q.TryDequeue(m) Then
            Console.WriteLine("Consumer Thread {0} - {1}", Thread.CurrentThread.ManagedThreadId, m.Body)
            Thread.Sleep(25)
          End If
        Loop Until cancelToken.IsCancellationRequested AndAlso q.Count = 0
        Console.WriteLine("Consumer Thread {0}: Done", Thread.CurrentThread.ManagedThreadId)
      End Sub
    
    End Module
    
    


    Tom Shelton
    Monday, April 18, 2011 5:17 AM
  • I'm curious though, why you can just start 2 threads and let them do their work until  you turn them off?  To be honest, looking at the code above it seems that you are making things more complicated then need to be... Can you explain to me what it is that the above is trying to accomplish?

    I m trying to start threads on message arrive event. Messages can come at any interval, so once I got two threads engaged, (two messages being processed) , I want to disable msmqreceiveevent till one thread done and then reregister the arriveevent to process next msg and so on. All this is controlled by my "count" variable which if holds the right value will produce right results.

    I understand the code looks ugly and complicated but it was all while looking for issue.

     

    Monday, April 18, 2011 11:38 PM
  • Ok... 2 threads will never process more then two messages at one time.  So, find one of hte many blocking queues out there, and have your threads just getting items off the queue.  Your even fires when a message is received - stick it on the queue, a threads wake up - queue is empty, they go back to sleep.  There really is no reason you have to start and stop your threads...
    Tom Shelton
    Tuesday, April 19, 2011 3:47 AM
  • One thing I want to bring to your attention that since I m using Aysnc style of processing MSMQ messages in which MSMQ itself process the messages in its own created thread which I dont even need to know. So basically I m not doing any explicit thread handling such as starting or ending them.

    Therefore just to control processing of message from MSMQ perspective, I m using the count variable that control the receiveevent of msmq which listens if there is any new message in que.

     

    Wednesday, April 20, 2011 3:22 PM
  • Well, there are problems with your model.   First it appears you are doing way to much work in an async event method - it is not usually a good idea to spend to much time in those events as they use the system thread pool.

    Second, your count variable is a shared resource - you are testing its value in several places in your method.  You do realize that the value may differ between each check?  Just because you sync the that actual updates of the value, doesn't guarentee that the value will be the same on the next check - because it could have been updated by another thread in the interim.  I should also mention that the synclocks and delegate calls to update the value are actaully quite unnecessary - System.Threading.Interlocked can handle those operations in a threadsafe and automic manner.

    All and all, I believe you would be much better served using a producer/consumer model.  The producer is your mq - the consumers are your processing threads.  You will then have complete control over the # of threads that can process at anyone time...  Otherwise, your going to need to use a different syncronization method - such as the System.Threading.Semaphore class which let's you control the # of threads that can access a resource at a given time.  But, it still seems easier to me to use a Producer/Consumer model...

    What version of the framework are you targeting?  I ask because, I can whip an example of the above - but, I want to make sure it's relavent.


    Tom Shelton
    Wednesday, April 20, 2011 6:56 PM
  • System.Threading.Interlocked - I m not using this class, is it something used internally by mq async events?

    I m working on 3.5

    Wednesday, April 20, 2011 10:42 PM