5

I am trying to use the ManualResetEventSlim class to communicate between a few parallel Tasks.

Here is a simplified version of the code:

class Program
{
    private static void Main(string[] args)
    {
        Stopwatch stopwatch = Stopwatch.StartNew();

        ManualResetEventSlim eventSlim = new ManualResetEventSlim(false);

        Task.Run(() =>
        {
            Task.Run(() =>
                            {
                                Thread.Sleep(1000);

                                eventSlim.Set();
                            });

            eventSlim.Wait();

            Console.WriteLine($"Hello from the Task! {stopwatch.Elapsed}");
        });

        eventSlim.Wait();

        Console.WriteLine($"Hello from the Main thread! {stopwatch.Elapsed}");

        stopwatch.Stop();

        Console.ReadLine();
    }
}

Most of the times the code runs fine and outputs:

Hello from main thread! 00:00:01.1017591
Hello from task! 00:00:01.1017630

However once in every five or six times I run the code I only get this output:

Hello from main thread! 00:00:01.1017591

And the code after the Wait inside the Task never gets called.

I'm using .NET Core 2.0.2 on Windows Server 2012 R2 with Visual Studio 15.4.1.

Can anyone reproduce this behaviour?

Can anyone confirm if my code is right or if there is any problems with it please?

Update After @ArhiChief suggested to test the results in the Release configuration, I figured that the problem only appears when I am using the IDE to debug my code.

When I build and run the code in the command line in either Debug/Release configuration, there seem to be no problems.

I tried to close/reopen the IDE and clean the project and do a fresh rebuild, and now the IDE seems to be working fine too.

Results: I haven't faced the issue so far after restarting the IDE, cleaning the project and doing a fresh rebuild of the project. I'm suspecting a minor bug in the IDE. However I can't report it because it has disappeared now.

I'm leaving this question open in case someone else encounters this issue and would want to track and report the bug.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Rojan Gh.
  • 1,062
  • 1
  • 9
  • 32
  • I cannot repro - tried it ~25 times. – Matthew Watson Oct 23 '17 at 09:55
  • Does it works same if you change build from debug to release version? – ArhiChief Oct 23 '17 at 09:56
  • Haven't tried it @ArhiChief I will update the question with the results. – Rojan Gh. Oct 23 '17 at 09:58
  • 2
    I don't know C# in depth, but maybe the problem is in GC what collects your objects because of no reference to it in your code. I mean, it looks like the situation what Jeffrey Richter describes in his book CLR via C# (4th edition) in Chapter 21 (Garbage Collection and Debug) – ArhiChief Oct 23 '17 at 10:02
  • Are you sure that when testing this you actually use the code you posted, including last `Console.ReadLine` call? – Evk Oct 23 '17 at 10:11
  • It might be so @ArhiChief because now it seems to be an IDE fault. See the updated question please. :) – Rojan Gh. Oct 23 '17 at 10:15
  • Yes @Evk the process was not terminated when I was waiting for the output. :) I was using the exact same code. :) – Rojan Gh. Oct 23 '17 at 10:16
  • 2
    It is a pretty classic threading race, the task's Console.WriteLine() races with the main thread's Console.ReadLine(). So a very simple explanation is that you pressed the Enter key too soon and the program terminated before the task could write its output. "Windows Server 2012" is a red flag, it dates back to the time that Microsoft still thought it was a good idea to interlock Console.Read and Console.Write, that created a lot of misery. And ".NET Core 2.0.2" is a red flag, they been tinkering heavily with the Console class to make it compatible with the Unixes. Just let them know. – Hans Passant Oct 23 '17 at 10:18
  • I will keep an eye on this and report it in case it happens again @HansPassant thanks for the information. :) – Rojan Gh. Oct 23 '17 at 10:20

1 Answers1

0

I'm also have an idea what your first task may be disposed and collected. Consider this code

static void Main(string[] args)
{
Stopwatch stopwatch = Stopwatch.StartNew();

ManualResetEventSlim eventSlim = new ManualResetEventSlim(false);

Task.Run(() =>
{
    Task.Run(() =>
    {
        Thread.Sleep(1000);

        eventSlim.Set();
    });

    eventSlim.Wait();

    Console.WriteLine($"Hello from the Task! {stopwatch.Elapsed}");
});

eventSlim.Wait();

Console.WriteLine($"Hello from the Main thread! {stopwatch.Elapsed}");

stopwatch.Stop();

//Console.ReadLine();
}

I get message Hello from the Main thread! 00:00:01.0111623. I also read that

A single call to Set() signals the event, and any waiting Tasks are released. New calls to Wait() don't block until Reset() method called.

But lets return to our code. If you rewrite it like this

static void Main(string[] args)
{
Stopwatch stopwatch = Stopwatch.StartNew();

ManualResetEventSlim eventSlim = new ManualResetEventSlim(false);

var t = Task.Run(() =>
{
    Task.Run(() =>
    {
        Thread.Sleep(1000);

        eventSlim.Set();
    });

    eventSlim.Wait();

    Console.WriteLine($"Hello from the Task! {stopwatch.Elapsed}");
});

eventSlim.Wait();

Console.WriteLine($"Hello from the Main thread! {stopwatch.Elapsed}");

stopwatch.Stop();

t.Wait();
//Console.ReadLine();
}

you will find that everything works as you expected.

ArhiChief
  • 240
  • 2
  • 13
  • Well I was having the issue while I was setting breakpoints in the IDE. And by stopping on a breakpoint, I was sure the process had not exited and is still running, therefore this can't be the reason. – Rojan Gh. Oct 23 '17 at 11:05