尝试在抛出错误之前运行任务n次

时间:2014-07-18 13:03:29

标签: c# async-await task-parallel-library .net-4.5

我有使用smtp服务器发送电子邮件的方法。使用Task.Factory我正在调用该方法来阻止UI:

Task.Factory.StartNew(() => SendMail("mail@example.com", "Test title", "TEST body"), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
.ContinueWith(p =>
        {
            if (p.IsFaulted)
            {
                if (p.Exception != null)
                {
                    MessageBox.Show(p.Exception.ToString());
                }
                return;
            }
             MessageBox.Show("ok");
        }, TaskScheduler.FromCurrentSynchronizationContext());

现在我想修改我的代码,以便在出现问题时尝试拨打SendMail 10次。我尝试过使用do / while块,但是我无法使用它:

    private void button1_Click(object sender, EventArgs e)
    {
        bool success = false;
        int i = 0;
        int max = 10;

        do
        {
            Task.Factory.StartNew(() => SendMail("mail@example.com", "Test", "TEST1"), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
                .ContinueWith(p =>
                {
                    if (p.IsFaulted)
                    {
                        if (p.Exception != null)
                        {
                            MessageBox.Show(p.Exception.ToString());
                        }
                        return;
                    }
                    success = true;
                    MessageBox.Show("ok");
                }, TaskScheduler.FromCurrentSynchronizationContext());
            i++;
        } while (!success && i < max);

        if (!success)
        {
            MessageBox.Show("error");
        }
        else
        {
            MessageBox.Show("ok", "success", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }
    }

    private void SendMail(string address, string title, string body)
    {
        Thread.Sleep(10000);
        MailClient.Instance.Send(address, title, body);
    }

我想做的就是能够在任务中调用Specific方法,如果我得到异常,那么我想一次又一次地调用它,10次,如果经过那10次就不会成功我想表现出异常。

2 个答案:

答案 0 :(得分:4)

这有点偏离主题,但每当我看到有人使用线程进行IO绑定操作时,我就会感到寒意:)

由于发送邮件是一种网络绑定操作,因此您可以使用.NET 4.5中添加的等待SmtpClient.SendMailAsync

如果我可以采取JoelC发布的实现并重构一下:

private int _maxAttempts = 10;

private async Task TrySendMailAsync(int attemptNumber)   
{
     var smtpClient = new SmtpClient();
     var mailMsg = new MailMessage("from@test.com", "to@test.com", "Test Subject", "Test Body");

     while (!success && attempts <= maxAttempts)
     {
         try
         {
             await smtpClient.SendMailAsync(mailMsg)).ConfigureAwait(false);
             success = true;
         }
         catch
         {
             if (attempts >= maxAttempts)
             {
                 throw;
             }
         }
         attempts++;
     }
}

这将允许您按照请求循环,但让作为异步IO的主作业无需执行线程池线程的unnessaceace。

答案 1 :(得分:1)

这样的事情可能会解决问题:

    private int _maxAttempts = 10;

    private void TrySendMail(int attemptNumber)

        Task.Factory.StartNew(() => SendMail("mail@example.com", "Test title", "TEST body"), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default)
        .ContinueWith(p =>
        {
            attemptNumber++;

            if (p.IsFaulted)
            {
                if (p.Exception != null)
                {
                    if (_attempts < _maxAttempts)
                    {
                        // Try again
                        TrySendMail(attemptNumber);
                    }
                    else
                    {
                        MessageBox.Show(p.Exception.ToString());
                    }
                }
                return;
            }
            success = true;
            MessageBox.Show("ok");
        }, TaskScheduler.FromCurrentSynchronizationContext());
    }

它不是最漂亮的,你想看到你不会递归地多次调用它并获得堆栈溢出!十次应该没问题。

编辑: 我将尝试次数更改为一个参数,以便更安全地使用线程,以防您在线程上多次调用此发送邮件。

EDIT2:

上面提到的方法@mazharenko的实现可能如下所示:

private void TryAndRepeat(Action routine, int maxAttempts)
    {
        int attempts = 1 ;
        bool success = false;

        while (!success && attempts <= maxAttempts)
        {
            try
            {
                routine.Invoke();

                success = true;
            }
            catch
            {
                if (attempts >= maxAttempts)
                {
                    throw;
                }
            }
            attempts++;
        } 
    }