HavenDV / H.Pipes

A simple, easy to use, strongly-typed, async wrapper around .NET named pipes.
MIT License
219 stars 26 forks source link

I can't transmit a simple message [question] #13

Closed atiris closed 2 years ago

atiris commented 2 years ago

I'm having trouble moving a simple message (my first step with this library). This is a minimal new .NET Framework 4.7.2 project (Desktop Application) with nothing only Load event for form. I only added libraries: H.Pipes and H.Formatters.System.Text.Json This is the entire form code.

using H.Formatters;
using H.Pipes;
using System;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Test1
{
    public partial class Frm_Test : Form
    {
        private PipeServer<MyMessage> server;
        private PipeClient<MyMessage> client;
        private string pipeName = "a";
        private string test = "THIS IS TEST MESSAGE";

        public Frm_Test() { InitializeComponent(); }

        private void Frm_Test_Load(object sender, EventArgs e)
        {
            server = new PipeServer<MyMessage>(pipeName, formatter: new SystemTextJsonFormatter());
            server.MessageReceived += (c, args) =>
            {
                Console.WriteLine("Received: " + args.Message.Text);
                if (args.Message.Text != test) { Console.WriteLine("WRONG MESSAGE"); }
            };
            Task.Run(async () => { await server.StartAsync(); });

            client = new PipeClient<MyMessage>(pipeName, formatter: new SystemTextJsonFormatter());
            client.Connected += (o, args) =>
            {
                Console.WriteLine("Sending: " + test);
                client.WriteAsync(new MyMessage { Text = test });
            };
            client.ConnectAsync();
        }
    }

    public class MyMessage { public string Text; }
}

Console output is like this (message.Text is always empty):

Sending: THIS IS TEST MESSAGE
Received: 
WRONG MESSAGE

I get the same problem even with two separated client-server applications.
Please advise me where I am making a mistake?

atiris commented 2 years ago

Hm, this is possible bug with SystemTextJsonFormatter. If I install H.Formatters.Newtonsoft.Json and change SystemTextJsonFormatter to NewtonsoftJsonFormatter, everything start working. Console log: Received: THIS IS TEST MESSAGE. Well, I really lost a lot of time with this. 🙄

HavenDV commented 2 years ago

Thanks for the problem. I'll check. I want to note that be sure to call Dispose before closing the program / ending the use of PipeServer/PipeClient, otherwise you may have problems after restarting. Pipes are the resource of the entire system and must be properly cleaned after use.

atiris commented 2 years ago

I want to note that be sure to call Dispose before closing the program

Hi @HavenDV thank you for this, I didn't really realize that. It would undoubtedly be a good idea to add this information to the readme.

HavenDV commented 2 years ago

I can't repeat it in tests:

[TestMethod]
public async Task ParallelTest()
{
    const string pipeName = "absolutely_random_name";
    const string message = "THIS IS TEST MESSAGE";

    var receivedMessage = string.Empty;

    var server = new PipeServer<string>(pipeName, formatter: new SystemTextJsonFormatter());
    server.MessageReceived += (c, args) =>
    {
        Console.WriteLine($"Received: {args.Message}");

        receivedMessage = args.Message;
    };
    var startTask = server.StartAsync();

    var client = new PipeClient<string>(pipeName, formatter: new SystemTextJsonFormatter());
    client.Connected += async (o, args) =>
    {
        Console.WriteLine($"Sending: {message}");

        await client.WriteAsync(message);

        Console.WriteLine($"Sent: {message}");
    };
    await client.ConnectAsync();
    await startTask;

    await Task.Delay(TimeSpan.FromMilliseconds(100));

    Assert.AreEqual(message, receivedMessage);
}

It works correctly.

I want to note that there are some peculiarities here when using asynchronous methods in Windows Forms. If you don't use ConfigureAwait(false), the default scheduler will be the UI scheduler. This can cause various problems with the initialization sequence. Look towards using async void which contains try/catch inside. Also read this - https://devblogs.microsoft.com/dotnet/configureawait-faq/

HavenDV commented 2 years ago

It would undoubtedly be a good idea to add this information to the readme.

Updated README, added Notes block to Usage

HavenDV commented 2 years ago

Also try using Task.Run in general when creating the PipeServer, it might help.

atiris commented 2 years ago

I can't repeat it in tests

Thanks, I applied to the code everything I understood. This article https://devblogs.microsoft.com/dotnet/configureawait-faq/ requires a more advanced programmer than me 😄

Your test has probably been simplified to a level where serialization is not used as with a normal object.
Modified version of unit test:

namespace HPipesTests
{
    public class MyMessage
    {
        public string Text;
    }

    [TestClass]
    public class SystemTextJsonFm
    {
        [TestMethod]
        public async Task MessageString()
        {
            const string pipeName = "random_751902396";
            const string message = "THIS IS TEST MESSAGE";

            var receivedMessage = string.Empty;

            var server = new PipeServer<string>(pipeName, formatter: new SystemTextJsonFormatter());
            server.MessageReceived += (c, args) =>
            {
                Console.WriteLine($"Received: {args.Message}");

                receivedMessage = args.Message;
            };
            var startTask = server.StartAsync();

            var client = new PipeClient<string>(pipeName, formatter: new SystemTextJsonFormatter());
            client.Connected += async (o, args) =>
            {
                Console.WriteLine($"Sending: {message}");

                await client.WriteAsync(message);

                Console.WriteLine($"Sent: {message}");
            };
            await client.ConnectAsync();
            await startTask;

            await Task.Delay(TimeSpan.FromMilliseconds(100));

            Assert.AreEqual(message, receivedMessage);
        }

        [TestMethod]
        public async Task MessageClass()
        {
            const string pipeName = "random_54821002374";
            const string message = "THIS IS TEST MESSAGE";

            var receivedMessage = string.Empty;

            var server = new PipeServer<MyMessage>(pipeName, formatter: new SystemTextJsonFormatter());
            server.MessageReceived += (c, args) =>
            {
                Console.WriteLine($"Received: {args.Message.Text}");

                receivedMessage = args.Message.Text;
            };
            var startTask = server.StartAsync();

            var client = new PipeClient<MyMessage>(pipeName, formatter: new SystemTextJsonFormatter());
            client.Connected += async (o, args) =>
            {
                Console.WriteLine($"Sending: {message}");

                await client.WriteAsync(new MyMessage() { Text = message });

                Console.WriteLine($"Sent: {message}");
            };
            await client.ConnectAsync();
            await startTask;

            await Task.Delay(TimeSpan.FromMilliseconds(100));

            Assert.AreEqual(message, receivedMessage);
        }
    }
}

Result: 1 Passed / 1 Failed

HavenDV commented 2 years ago

Thanks, you're right, the problem started when I used your MyMessage class.

https://stackoverflow.com/questions/58139759/how-to-use-class-fields-with-system-text-json-jsonserializer It is described in detail here. You just need to use the property.

public class MyMessage
{
    public string Text { get; set; }
}

There is also an option here to add JsonSerializerOptions { IncludeFields = true } to the SystemTextJsonFormatter. I need to understand what caused the refusal to serialize fields in the System.Text.Json library in order to make this decision.

HavenDV commented 2 years ago

For now, I've just added the ability to customize the SystemTextJsonFormatter via the Options property:

var server = new PipeServer<MyMessage>(pipeName, formatter: new SystemTextJsonFormatter { Options = { IncludeFields = true } });