C#: 如何使用线程并知道线程工作何时完成?

3

我在我的C# Win Form项目中读取未读邮件。有时会附带一些文件,下载这些文件需要一些时间,因此在下载完成期间,我的项目UI将被锁定。为了解决这个问题,我使用线程池。

以下是我的代码:

    System.Threading.ThreadPool.QueueUserWorkItem((o) =>
    {
        for (int i = 0; i < lstEmailAddress.Count; i++)
        {
            Get(imap[i], lstEmailAddress[i].MailBox, out Emails[i]);
        }

        this.BeginInvoke(new Action(() =>
        {
            for (int i = 0; i < lstEmailAddress.Count; i++)
            {
                for (int j = 0; j < Emails[i].Count; j++)
                {
                    Database.EmailRecieve_Insert(Emails[i][j]);
                }

                arrEmailUserControl[i].txtRecievedCount.Text = (Convert.ToInt32(arrEmailUserControl[i].txtRecievedCount.Text) + Emails[i].Count).ToString();
            }

        }));
    });

这是我使用的 Get 方法:

private bool Get(Imap4Client imap, string[,] MailBox, out List<EmailStruct> Emails)
    {
        Emails = new List<EmailStruct>();
        try
        {
            for (int i = 0; i < MailBox.GetLength(0); i++)
            {
                Mailbox inbox = imap.SelectMailbox(MailBox[i, 1]);

                int[] ids = inbox.Search("UNSEEN");

                if (ids.Length > 0)
                {
                    ActiveUp.Net.Mail.Message msg = null;

                    for (var j = 0; j < ids.Length; j++)
                    {
                        msg = inbox.Fetch.MessageObject(ids[j]);

                        EmailStruct email = new EmailStruct();
                        email.SourceAddress = msg.From.Email;
                        email.Subject = msg.Subject;
                        email.Text = msg.BodyText.Text.ToString();

                        msg.Attachments.StoreToFolder(InternalConstant.AttachmentFolder);

                        email.MailBoxID = Convert.ToInt32(MailBox[i, 0]);

                        Emails.Add(email);
                    }
                }
            }
            return true;
        }
        catch (Exception)
        {
            return false;
        }
    }

如您在以上代码中所看到的,我有一个操作,正好在线程工作完成时运行。实际上,我想通知线程工作已经完成。

但是问题在于:大部分情况下,当我想要从收件箱中获取消息时,都会出现此异常。

The Read method cannot be called when another read operation is pending

如果我移除线程池,程序会正常运行。 我使用线程池只是为了避免UI锁定并知道线程何时完成。

你能否建议更好的方式? 感谢任何帮助...


2
还没清醒到可以给出完整的答案,但我建议放弃使用 Thread 类,改用 Tasks。你可以创建一个任务,并使用其 ContinueWith 属性指定任务完成时执行的操作:Task t = Task.Run(() => DoWork()).ContinueWith(() => AfterWorkDone());(从 VB.net 自动翻译,希望正确)。或者你可以使用 AsyncAwait 在代码中等待任务完成,而不会锁定用户界面。 - Jens
听起来好像在第一个线程执行完之前就开始获取电子邮件了。添加作业到线程池的代码是什么样的? - Mattis
没有足够的信息来提供答案。异常发生在哪里?完整的堆栈跟踪是什么?是什么导致了操作的启动?为什么在上一个操作完成之前,操作(显然)再次被启动?还有其他可以做的事情来帮助改进上面的代码,但首先需要解决您正在询问的特定问题。请提供一个好的、_最小化的、_完整的_代码示例,可靠地重现问题。另请参阅http://stackoverflow.com/help/how-to-ask。 - Peter Duniho
不要简单粗暴地忽略异常。简单来说,你可以这样做:捕获异常 -> 你知道该如何处理它并解决问题吗?如果是 -> 处理/解决。如果否 -> 记录异常日志(不要忘记记录异常堆栈和内部异常)。确保你的模块没有处于一半修改的状态。如果是,回滚状态更改。如果异常发生在低层代码 -> 很可能你需要重新抛出它,并让高层代码决定处理方式。 - nightcoder
1个回答

5

您有更好的建议吗?

以下是我在我的WPF应用程序中使用现代方法(如果您在2020年后阅读此内容,则不再适用)的做法:

private async void DoSomethingAsync()
{
    try
    {
        DoanloadEmailsCommand.IsEnabled = false;

        await Task.Run(() =>
        {
            // Do something here. For example, work with the database, download emails, etc.
            Emails = DownloadEmails();
        });
    }
    finally
    {
        DoanloadEmailsCommand.IsEnabled = true;
    }

    MessageBox.Show(string.Format("{0} emails have been downloaded.", Emails.Count));
}

传递给Task.Run()方法的lambda表达式中的代码将在另一个线程上执行。
创建并运行任务后,控制流会立即返回给调用者(在我们的例子中最终返回到UI消息循环,该循环将继续处理其他事件)。
当任务完成时,某些“唤醒”消息将发送到应用程序的消息队列中,当消息循环接收并处理此消息时,执行将继续从离开的地方继续进行。
在我们的例子中,当任务完成时,执行将从“await Task.Run()”调用的下一行继续在主线程上进行。
因此,命令对象将启用(它将绑定到按钮上,比如“下载邮件”,所以按钮也将变为启用状态),并且将显示一个消息框,说有多少封电子邮件已经被下载。

正如您所看到的,代码非常干净易读易懂(一旦您知道使用async/await关键字和Task类的基础知识)。

(还请阅读我上面关于异常处理的评论)


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