不确定我是否正确地使用了C#中的“using”关键字,在tftp应用程序中。

3

我正在尝试使用这个预先制作的C# tftp服务器应用程序和我的Windows C#表单一起使用。在作者的服务器示例中,他使用控制台应用程序,效果很好。当我尝试将他的控制台示例移植到我的表单应用程序中时,它无法工作(没有错误,只是无法连接),我认为问题在于“using”语句:

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

我不确定我是否理解正确,但我认为Console.Read()会阻止应用程序退出。如果是这种情况,如何在窗体应用程序中实现等效的功能呢?我无法理解"using"。对不起,我还是C#的新手。

4个回答

5

Windows Forms会一直保持打开状态,直到用户明确地关闭它们。它们始终有一个线程读取消息队列以获取用户输入,因此它们不会像未受限制的控制台应用程序一样退出。在Windows Forms中,我们比在控制台应用程序中更需要关注多线程和并发性问题。这大多数情况下是很自然的,但不总是如此。

正因为如此,你不能真正使用与Console.Read()等效的方法来延迟执行using处置,直到用户请求它。如果这样做,你的表单将会显得无响应。

然而,你很幸运!在C#中,using块只是记住在完成对象后调用IDisposable.Dispose()的语法糖。因此,在Forms项目中,相当于这个的做法就是把server对象存储为类范围内的字段,然后在例如Button.Click事件上调用server.Dispose()。当然,这只是一个例子。如果感觉更合适,也可以在Form.Closing上进行。

高级别地说,你想要做以下几件事:

  1. 在你的表单类中声明一个字段TftpServer server;
  2. 在构造函数中注册一个Load事件和你需要的一切来使server正常运行。
  3. Form_Load事件中打开你的server字段。
  4. 在表单生命周期中使用server的事件,如你所需。你可能需要考虑并发性,但这是另一个问题。
  5. 在表单的Dispose事件中调用server.Dispose()

实质上,

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;
        }
    }
}

你甚至不需要显式地调用Dispose方法,只要让垃圾回收器为你完成工作,当窗体对象超出范围时即可。 - Victor Zakharov
@Neolisk 确实,说得好,你可以让GC处理它。但我倾向于明确地表达。这只是减少了任何错误的风险。很难知道GC何时会出现,所以我认为在这种情况下,它应该被视为备用策略,以防有未处理的异常或类似情况发生。 - Matthew Haugen
重写 Form 的 Dispose 方法而不是使用 Closing 事件可能会更好。此外,@Neolisk,你应该手动调用 Dispose,因为你不知道是否有一个终结器来调用 Dispose,即使有,那也不是 IDisposable 的预期用法。 - Selali Adobor
@AssortedTrailmix,这非常正确。我完全忘记了覆盖Dispose。自从我上次使用Windows Forms以来已经过去太久了。我的更新语法是正确的,对吗?我现在不在WinForms编译器附近进行测试,但我认为它是正确的。我只是想确保我没有犯一些愚蠢的错误。 - Matthew Haugen
唯一需要注意的是,非空对象可以被处理,你能做的只有捕获 ObjectDisposedException 异常。 - Selali Adobor
太棒了,我明白了!感谢你的写作! - Nimjox

3
问题在于释放服务器就是关闭服务器。请记住,使用 using 关键字只是语法糖。以下两个代码块在实际上是等价的:
var foo = new Foo();
try
{
   foo.Do();
}
finally
{
   foo.Dispose();
}

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

在控制台应用程序中,阻塞主线程退出是可以的,但在窗体应用程序中则不同。问题不在于您需要通过执行某种阻塞操作来保持线程在使用中。那样做是不好的,行为会锁定您的表单应用程序。问题是您不想使用 "using"。您希望在启动服务器时将其实例化,然后在应用程序退出或停止单击时明确使用Dispose()进行处理。


1

这是一个小提示,也可以作为答案,你可以在这里使用using块,只需将其放在主函数中:

...(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
}

我忘了提到的另一个选项是在你的窗体中覆盖Dispose方法。你可能想要这样做。使用此选项,您可以确保您的服务器将被处理(除非有某些事件会阻止它被处理,例如内存不足)。


1
在控制台应用程序中,您的 TftpServer 实例会一直监听,直到线程退出,在按下键后才会检测到该键,这是通过 Console.Read() 实现的。
在您的窗体应用程序中,Console.Read() 不会等待,因此 using 块就完成了,这导致您的服务器实例超出范围。
因此,您并没有完全错误地使用 using,而是旨在使用并没有帮助您。请查看使用 任务并行库 让一些后台任务异步运行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接