如何使用任务、后台线程或其他方法解决 Windows 服务中的挂起问题

How to use tasks, background threads or another method to solve a hanging issue in Windows Service

我想知道解决这个问题的最佳方法。

我创建了一个 Windows 连接到邮箱的服务,处理电子邮件,然后自行清理,等待一定时间并重复。

protected override void OnStart(string[] args)
{
    this._mainTask = new Task(this.Poll, this._cancellationToken.Token, TaskCreationOptions.LongRunning);
    this._mainTask.Start();
}

private void Poll()
{
    CancellationToken cancellation = this._cancellationToken.Token;
    TimeSpan interval = TimeSpan.Zero;

    while (!cancellation.WaitHandle.WaitOne(interval))
    {
        using (IImapClient emailClient = new S22ImapClient())
        {
            ImapClientSettings chatSettings = ...;

            emailClient.Connect(chatSettings); // CAN SOMETIMES HANG HERE

            // SOME WORK DONE HERE
        }

        interval = this._waitAfterSuccessInterval;

        // check the cancellation state.
        if (cancellation.IsCancellationRequested)
        {
            break;
        }
    }
}

现在我正在使用第 3 方 IMAP 客户端 "S22.Imap"。当我有时创建电子邮件客户端对象时,它会在尝试登录时挂起。这反过来会无限期地挂起我的 Windows 服务。

public class S22ImapClient : IImapClient
{
    private ImapClient _client;

    public void Connect(ImapClientSettings imapClientSettings)
    {
        this._client = new ImapClient(
            imapClientSettings.Host,
            imapClientSettings.Port,
            imapClientSettings.EmailAddress,
            imapClientSettings.Password,
            AuthMethod.Login,
            true);
    }
}

我如何更改 "S22ImapClient.Connect()" 调用以在幕后使用某种方法尝试连接一段时间,如果无法连接则中止?

这个解决方案也将用于我需要用邮件客户端做的任何事情,例如"GetMessage()"、"DeleteMessage()"等

您可以使用取消令牌源,并在它挂起太久的情况下给它一个时间来取消。否则,您只需要扩展第三方 class 并实现 Connect 方法的异步版本。这是未经测试的,但应该给你基本的想法。

private void Poll()
{
    CancellationTokenSource source = new CancellationTokenSource();
    TimeSpan interval = TimeSpan.Zero;

    while (!source.Token.WaitHandle.WaitOne(interval))
    {
        using (IImapClient emailClient = new S22ImapClient())
        {
            ImapClientSettings chatSettings = ...;

            var task = Task.Run(() => 
            {
                source.CancelAfter(TimeSpan.FromSeconds(5));
                emailClient.Connect(chatSettings); // CAN SOMETIMES HANG HERE                   
            }, source.Token);

            // SOME WORK DONE HERE
        }

        interval = this._waitAfterSuccessInterval;

        // check the cancellation state.
        if (source.IsCancellationRequested)
        {
            break;
        }
    }
}

我决定停止使用 S22.Imap 电子邮件客户端来解决这个特定问题,并使用另一个第 3 方组件 ActiveUp MailSystem,因为它包括开箱即用的异步调用。

这样我就可以做这样的代码:

IAsyncResult connectResult = this._client.BeginConnectSsl(imapClientSettings.Host, imapClientSettings.Port, null);

if (!connectResult.AsyncWaitHandle.WaitOne(this._connectionTimeout))
{
    throw new EmailTimeoutException(this._connectionTimeout);
}