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

C# 在抛出错误之前尝试运行任务n次,c#,async-await,task-parallel-library,.net-4.5,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 => {

我的方法是使用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);
    }
private void按钮1\u单击(对象发送者,事件参数e)
{
布尔成功=假;
int i=0;
int max=10;
做
{
Task.Factory.StartNew(()=>SendMail(“mail@example.com,“测试”,“测试1”),CancellationToken.None,TaskCreationOptions.None,TaskScheduler.Default)
.ContinueWith(p=>
{
如果(p.IsFaulted)
{
如果(p.Exception!=null)
{
Show(p.Exception.ToString());
}
返回;
}
成功=真实;
MessageBox.Show(“ok”);
},TaskScheduler.FromCurrentSynchronizationContext());
i++;
}而(!success&&i

我想做的是能够在任务内部调用特定的方法,如果我得到异常,那么我想一次又一次地调用它,10次,如果在这10次之后它不会成功,我想显示异常。

类似的东西可能会解决问题:

    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());
    }
private int\u max=10;
专用void TrySendMail(int attemptNumber)
Task.Factory.StartNew(()=>SendMail(“mail@example.com,“测试标题”,“测试正文”),CancellationToken.None,TaskCreationOptions.None,TaskScheduler.Default)
.ContinueWith(p=>
{
attemptNumber++;
如果(p.IsFaulted)
{
如果(p.Exception!=null)
{
如果(_尝试次数<_最大尝试次数)
{
//再试一次
TrySendMail(attemptNumber);
}
其他的
{
Show(p.Exception.ToString());
}
}
返回;
}
成功=真实;
MessageBox.Show(“ok”);
},TaskScheduler.FromCurrentSynchronizationContext());
}
它不是最漂亮的,你要注意不要递归调用它太多次,从而导致堆栈溢出!十次就可以了

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

编辑2:

上面提到的@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++;
        } 
    }
private void TryAndRepeat(操作例程,int-maxtures)
{
int=1;
布尔成功=假;
而(!success&&attempts=maxtempts)
{
投掷;
}
}
尝试++;
} 
}

这有点离题,但每次我看到有人在IO绑定操作中使用线程时,我都会感到寒心:)

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

如果我可以接受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++;
     }
}
private int\u max=10;
专用异步任务TrySendMailAsync(int attemptNumber)
{
var smtpClient=新的smtpClient();
var mailMsg=新邮件消息(“from@test.com", "to@test.com“,”试验对象“,”试验机构“);
而(!success&&attempts=maxtempts)
{
投掷;
}
}
尝试++;
}
}

这将允许您根据请求进行循环,但让作为异步IO的主作业在不需要执行线程池线程的情况下工作。

我没有阅读代码。我只想告诉你,我知道postsharp可以很容易地做到这一点,如果你想查看它的话。只需记下它在变量中被调用了多少次。如果计数小于10,则更新变量,否则抛出异常。@bikie我这样做了,但smtp客户端中出现错误,说我已经在调用方法
send
,除了调用我的代码后,我还收到
error
消息。@vgSefa-我想避免购买额外的组件。
Task.Factory.StartNew()=>TryAndRepeate(SendMail(…),10),…)
从自身调用同一个方法是否危险?唯一的危险是从自身调用同一个方法太多次会导致堆栈空间不足。我认为线程不应该引起问题,因为您在尝试发送邮件之后再次调用它,因为调用是在
ContinueWith
中进行的。然而,mazharenko上面的评论可能暗示了一种更好的方法,即在单个任务中包含所有内容,而不是嵌套任务。