3

I'm trying to use this pre-made C# tftp server app with my windows c# form. In the authors server example, which works great, he uses a console app. When I trying porting his console example into my form app it doesn't work (no errors, just doesn't connect) and I believe my issue is in the "using" statement:

using (var server = new TftpServer())
{
    server.OnReadRequest += new TftpServerEventHandler(server_OnReadRequest);
    server.OnWriteRequest += new TftpServerEventHandler(server_OnWriteRequest);
    server.Start();
    Console.Read();
}

Not sure if I understand correctly but I believe the Console.Read() blocks keeping the app from exiting. If this is the case how would I implement a equivalent with a form app. I just can't get my head around the "using". Sorry I'm new to c#.

sessyargc.jp
  • 573
  • 5
  • 15
Nimjox
  • 1,271
  • 5
  • 18
  • 33

4 Answers4

5

Windows Forms will always remain open until they're explicitly closed by the user. They always have a thread reading the message queue for user input, so they won't exit the same way an unrestrained console application will. In Windows Forms, we have to worry a bit more about multithreading and concurrency than we would in console apps. It mostly comes naturally, but not always.

Because of that, you can't really use an equivalent to Console.Read() to hold off execution of the using disposal until the user requests it. If you did, your form would simply appear unresponsive.

However, you're in luck! A using block in C# is nothing more than syntactic sugar for remembering to call IDisposable.Dispose() after you're done with an object. So the equivalent to this in a Forms project could just be storing the server object in a class-wide field, then calling server.Dispose() on, say, a Button.Click event. That's, of course, just an example. You could also do it on Form.Closing if that felt more appropriate.

High-level, you want to do something like this:

  1. Declare a field in your form class TftpServer server;.
  2. Register a Load event and whatever you need for your server to function in your constructor.
  3. Open your server field in the Form_Load event.
  4. Use the server's events as you see so fit during the life of your Form. You may or may not have to worry about concurrency, but that's a matter for another question.
  5. Call server.Dispose() in the form's Dispose event.

In essence,

class main : Form
{
    private TftpServer server;

    public main()
    {
        InitializeComponent();

        this.Load += main_Load;

        server = new TftpServer();
        server.OnReadRequest += new TftpServerEventHandler(server_OnReadRequest);
        server.OnWriteRequest += new TftpServerEventHandler(server_OnWriteRequest);
    }

    private void main_Load(object sender, EventArgs e)
    {
        server.Start();
    }

    private void server_OnReadRequest(/* I wasn't sure of the arguments here */)
    {
        // use the read request: give or fetch its data (depending on who defines "read")
    }
    private void server_OnWriteRequest(/* I wasn't sure of the arguments here */)
    {
        // use the write request: give or fetch its data (depending on who defines "write")
    }

    protected override void Dispose(bool disposing)
    {
        if (server != null) // since Dispose can be called multiple times
        {
            server.Dispose();
            server = null;
        }
    }
}
Matthew Haugen
  • 12,916
  • 5
  • 38
  • 54
  • You may not even need to explicitly call Dispose, just let garbage collector do the work for you, as soon as the form object goes out of scope. – Victor Zakharov Jul 31 '14 at 23:40
  • @Neolisk true, good point, you could just let the GC handle it. But I tend to prefer being explicit. It just lessens the risk of any errors all 'round. It's hard to know when the GC will come around, so I'd say in a case like this, it should be considered a back-up strategy for if there's an unhandled exception or something of that sort. – Matthew Haugen Jul 31 '14 at 23:45
  • Overriding Dispose for the Form instead of using the Closing event would probably work better. Also @Neolisk you should be calling Dispose manually, you have no idea if there's a finalizer for the server class to call Dispose, and even if there is, that's not the intended use of IDisposable. – Selali Adobor Jul 31 '14 at 23:45
  • @AssortedTrailmix that's very true, all of it. I totally forgot about overriding `Dispose`. It's been far too long since I worked with Windows Forms. My updated syntax is right, right? I'm not near a WinForms compiler at the moment to test it, but I think it's right. I just want to make sure I didn't make some stupid mistake on it. – Matthew Haugen Jul 31 '14 at 23:51
  • The only thing to watch out for is that a non-null object can be Disposed, the only thing you can do is catch an ObjectDisposedException. – Selali Adobor Aug 01 '14 at 05:21
  • Awesome, I got it! Thanks for the write up! – Nimjox Aug 01 '14 at 15:12
3

The problem is that disposing the server is what is closing it. Keep in mind using is just syntactic sugar. The following two code chunks are [practically] equivalent:

var foo = new Foo();
try
{
   foo.Do();
}
finally
{
   foo.Dispose();
}

using (var foo = new Foo())
{
   foo.Do();
}

You are fine blocking the main thread from exiting in a Console app, but in a Forms app it's different. The problem is not that you need to hold the thread inside the using by doing some sort of blocking operation. That would be bad, and the behavior would lock up your forms app. The problem is you don't want to use using. You want to new it up when you start the server, and then later on, on application exit, or on a stop click, explicitly dispose it with Dispose().

Keith
  • 330
  • 1
  • 6
1

In a console application your TftpServer instance is listening until the thread exits which is only after a key is pressed which is detected by Console.Read()

In your forms app that Console.Read() isn't waiting around and so the using block finishes and that causes your server instance to fall out of scope.

So you are not exactly misusing the using but rather the intended use is not helping you at all. Take a look at using the task parallel library to let some background tasks run asynchronously.

clarkitect
  • 1,720
  • 14
  • 23
1

A small note that also doubles as an answer, you could use a using block here, you just put it in your main function:

...(make your form and stuff) 
using (var server = new TftpServer())
{
   server.OnReadRequest += new TftpServerEventHandler(server_OnReadRequest);
   server.OnWriteRequest += new TftpServerEventHandler(server_OnWriteRequest);
   server.Start();
   Application.Run(yourFormHere); //This blocks until the form is closed
}

Another option I forgot to mention is overriding Dispose in your Form. You probably want to do this. With this option you're guaranteed your server will be disposed (bar some event that would prevent it from being disposed either way [ie. being out of memory])

Selali Adobor
  • 2,060
  • 18
  • 30