5

I have a long running process that sends data to the other machine. But this data is received in chunks (like a set of 100 packets, then delay of minimum 10 seconds).

I started the sending function on a separate thread using Task.Run(() => { SendPackets(); });

The packets to be sent are queued in a Queue<Packet> object by some other function.

In SendPackets() I am using a while loop to retrieve and send (asynchronously) all items available in the queue.

void SendPackets()
{
    while(isRunning)
    {
       while(thePacketQueue.Count > 0)
       {
          Packet pkt = thePacketQueue.Dequeue();

          BeginSend(pkt, callback);   // Actual code to send data over asynchronously
       }

       Task.Delay(1000);  // <---- My question lies here
    }
 }

All the locks are in place!

My question is, when I use Task.Delay, is it possible then the next cycle may be executed by a thread different from the current one?

Is there any other approach, where instead of specifying delay for 1 second, I can use something like ManualResetEvent, and what would be the respective code (I have no idea how to use the ManualResetEvent etc.

Also, I am new to async/await and TPL, so kindly bear with my ignorance.

TIA.

Manish Dalal
  • 1,768
  • 1
  • 10
  • 14
  • 1
    If you're trying to use thread state or otherwise need thread affinity you're probably doing things wrong to start with. Unfortunately, there's not enough code here for us to see *where* you're trying to depend on thread affinity. Also, *which* [`Queue`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.queue-1?view=netframework-4.8#thread-safety) are you using? – Damien_The_Unbeliever Mar 06 '20 at 08:28

1 Answers1

10

My question is, when I use Task.Delay, is it possible then the next cycle may be executed by a thread different from the current one?

Not with the code you've got, because that code is buggy. It won't actually delay between cycles at all. It creates a task that will complete in a second, but then ignores that task. Your code should be:

await Task.Delay(1000);

or potentially:

await Task.Delay(1000).ConfigureAwait(false);

With the second piece of code, that can absolutely run each cycle on a different thread. With the first piece of code, it will depend on the synchronization context. If you were running in a synchronization context with thread affinity (e.g. you're calling this from the UI thread of a WPF or WinForms app) then the async method will continue on the same thread after the delay completes. If you're running without a synchronization context, or in a synchronization context that doesn't just use one thread, then again it could run each cycle in a different thread.

As you're starting this code with Task.Run, you won't have a synchronization context - but it's worth being aware that the same piece of code could behave differently when run in a different way.

As a side note, it's not clear what's adding items to thePacketQueue, but unless that's a concurrent collection (e.g. ConcurrentQueue), you may well have a problem there too.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 2
    They've shown they're using `Task.Run` to start (the code they've shown) running. I think that means no sync context. – Damien_The_Unbeliever Mar 06 '20 at 08:29
  • 1
    @Damien_The_Unbeliever: Yup, will edit. – Jon Skeet Mar 06 '20 at 08:31
  • Could `SpinWait` be used as well? To my understanding, that would even allow you to provide a predicate to evaluate exactly when it should stop spinning, and also prevent context switching, which could be rather expensive depending on the scenario. https://learn.microsoft.com/en-us/dotnet/standard/threading/spinwait – marcofo88 Mar 06 '20 at 09:11
  • 1
    @marcofo88: I doubt that SpinWait is an appropriate option here. If the OP really needs to stay on the same thread, `Thread.Sleep` would probably be the simplest option. – Jon Skeet Mar 06 '20 at 09:18