C#线程被多次调用但只运行一次

4

基本上,我有一个带有按钮的表单,当按下按钮时,它会创建一个运行线程的类的实例。当线程完成时,它会自动调用Thread.Abort()。

我目前拥有的代码如下:

按钮:

private void Buttonclick(object sender, EventArgs e)
{
     MyClass c = new MyClass()
     c.Do_your_thing();
}

类:

public class MyClass
{
    Thread t;

    public void Do_your_thing()
    {
         t = new Thread(Running_code);
         t.Start();
    }

    private void Running_code()
    {
         //Perform code here
         t.Abort();
    }
}

当我点击按钮一次时,一切正常。但是当我再次按下按钮时,什么也没有发生。
如果我不使用t.Abort(),则一切都正常。但是不使用t.Abort()会导致内存泄漏,程序无法正确关闭(线程未关闭,因此进程将保持活动状态)。
有人可以解释一下发生了什么吗?我该如何修复它?
编辑:根据要求,我发布一些实际代码。
public class MyClass
{
    public void Test()
    {
        t = new Thread(() =>
            {
                wb.DocumentCompleted += get_part;
                wb.Navigate("http://www.google.com");
                Application.Run();
            });

        t.SetApartmentState(ApartmentState.STA);
        t.Start();
    }

    public void get_part(object sender, WebBrowserDocumentCompletedEventArgs e)
    {
        var br = sender as WebBrowser;
        string url = e.Url.ToString();

        //Here is some code that compares the url to surten predefined url. When there is a match, it should run some code and then go to a new url

        if(url == string_final_url)
        {
            //Finally at the url I want, open it in a new Internet Explorer Window
            Process proc = Process.Start("IExplore.exe", url);           
        }
    }
}

这是一个小型网页爬虫程序的一部分。它导航到需要一些登录信息的网页。当我抵达实际想要的页面时,它应该在新的Internet Explorer中打开。
当我调用这段代码并关闭表单后,它仍然在进程树中可见。当我多次点击按钮时,使用的内存不断增加,我怀疑这是某种内存泄漏。

1
尝试使用t.Join()代替Abort - Jahan Zinedine
2
(顺便说一句:永远不要调用 Thread.Abort()。你会后悔的。) - Matthew Watson
@OP:请发表更具代表性的代码。您发布的代码在没有t.Abort()的情况下也可以正常工作。 - Matthew Watson
但是如果不使用t.Abort()会导致内存泄漏 - 除非你还做了其他错误的操作。从传递给new Thread()的方法中return(或达到最后的})应该足以终止该线程。 - Damien_The_Unbeliever
你可能需要进一步解释“一切正常”实际上意味着什么 - 你如何确定?你几乎肯定需要展示更多的代码。 - Damien_The_Unbeliever
3
这里有一本关于线程的优秀电子书:Albahari第一页中部分有一个关于"前台线程和后台线程"的章节。请注意,如需进一步了解,请访问原网站。 - Nick Butler
2个回答

2
首先,永远不要使用 Thread.Abort()。有关原因,请参见 Is this thread.abort() normal and safe?
在网络上有很多关于使用Thread.Abort()的警告。我建议除非真的需要,否则避免使用它,在这种情况下,我认为不需要使用它。你最好实现一个一次性定时器,可能是半秒超时,并在每个按键操作后重置它。这样,只有在用户不活动半秒钟或更长时间(或您选择的任何长度)之后才会进行昂贵的操作。

不必使用 abort,你可以使用 Join() 方法。该方法会阻塞调用线程,直到一个线程终止。

它的使用示例如下:

Thread t1 = new Thread(() => 
{ 
    Thread.Sleep(4000);
    Console.WriteLine("t1 is ending.");
});
t1.Start();

Thread t2 = new Thread(() => 
{ 
    Thread.Sleep(1000);
    Console.WriteLine("t2 is ending.");
});
t2.Start();

t1.Join();
Console.WriteLine("t1.Join() returned.");

t2.Join();
Console.WriteLine("t2.Join() returned.");

我希望这有所帮助。
编辑。回答您的评论; 调用Join()是释放线程的方法。您不需要做其他任何事情。只需确保线程在退出之前清理它们可能正在使用的任何资源即可。
话虽如此,我建议您考虑使用线程池或任务并行库(TPL),而不是显式地管理线程。它们更容易使用,并且可以更顺畅地处理这种情况。

1
如果你看代码的话,似乎他要求线程加入到自身... Running_code() 是线程,它可以访问代表自己的 Thread 对象 t。非常混乱! - Matthew Watson
同意。我也同意上面的评论,认为上面的代码运行良好。但这并不改变Abort()很糟糕的事实。 - MoonKnight
如果在函数内部调用线程对象不是最佳实践,我该如何确保函数执行完毕时线程被销毁? - Jordy
@Jordy 请看我上面的编辑... - MoonKnight
啊,我错过了那个,谢谢! - Jordy
如果您认为这是答案,请相应地标记为答案...祝您一切顺利,好运:] - MoonKnight

1

如果您能够使用 .net 4+,那么您可以使用 TPL(任务并行库)来大大简化此过程。

public class MyClass
    {
        public void Do_your_thing()
        {
            // for async execution
            Task.Factory.StartNew(Running_code);

            // for synchronous execution
            // CAUTION !! If invoked from UI thread this will freeze the GUI until Running_code is returned.
            //Task.Factory.StartNew(Running_code).Wait(); 
        }

        private void Running_code()
        {
           Thread.Sleep( 2000 );
           Debug.WriteLine( "Something was done" );
        }
    }

此外,如果Running_Code方法正在进行一些IO操作,TPL可以利用IO完成端口,操作可能完全无需线程。
编辑:请查看此SO线程。 在新线程中使用WebBrowser控件
显然,WebBrowser控件与非UI线程不兼容。

这看起来是一个非常好的解决方案,我发布了一些更多的代码,显示我正在使用WebBrowser对象。它必须在一个线程中运行,所以我不确定我能否使用这个解决方案。 - Jordy

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