попробуйте запустить задачу n раз, прежде чем выдаст ошибку

У меня есть метод отправки электронной почты с помощью SMTP-сервера. Используя Task.Factory, я вызываю этот метод, чтобы не блокировать пользовательский интерфейс:

Task.Factory.StartNew(() => SendMail("[email protected]", "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("[email protected]", "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);
    }

Что я хотел бы сделать, так это иметь возможность вызывать конкретный метод внутри задачи, если я получаю исключение, я хотел бы вызывать его снова и снова, 10 раз, если после этих 10 раз он не будет успешным, я хочу показать исключение.


person Misiu    schedule 18.07.2014    source источник
comment
Я не читал код. Я просто хочу сказать, я знаю, что PostSharp может сделать это легко, если вы хотите это проверить.   -  person Sefa    schedule 18.07.2014
comment
Просто подсчитайте, сколько раз он был вызван в переменной. Если счетчик меньше 10, обновите переменную, в противном случае вызовите исключение.   -  person Beakie    schedule 18.07.2014
comment
@Beakie Я сделал это, но у меня появляется ошибка в smtp-клиенте, говорящая, что я уже вызываю метод send, кроме того, после вызова моего кода я получаю сообщение error.   -  person Misiu    schedule 18.07.2014
comment
@vgSefa - я бы не хотел покупать лишние компоненты.   -  person Misiu    schedule 18.07.2014
comment
Цикл не будет работать, потому что вы немедленно выполните конец цикла, даже когда ваша первая задача запускается. Скорее всего, вы закончите свои десять циклов, пока первая отправка почты еще происходит.   -  person JoelC    schedule 18.07.2014
comment
Может, лучше обернуть do-пока только SendMail?   -  person mazharenko    schedule 18.07.2014
comment
@JoelC - вот что я подумал. Есть какие-нибудь советы, как это исправить?   -  person Misiu    schedule 18.07.2014
comment
@mazharenko хорошая идея, я попробую, но я хотел бы знать, как это сделать с задачами, потому что я не смогу изменить каждый метод, который я хотел бы вызвать таким образом.   -  person Misiu    schedule 18.07.2014
comment
@mazharenko хм, выглядит интересно, есть ли шансы показать мне образец тела TryAndRepeat?   -  person Misiu    schedule 18.07.2014
comment
Я думал в духе @mazharenko и добавил аналогичный ответ ниже.   -  person JoelC    schedule 18.07.2014


Ответы (2)


Это немного не по теме, но каждый раз, когда я вижу, что кто-то использует потоки для операций, связанных с вводом-выводом, меня охватывает мурашки :)

Поскольку отправка почты связана с сетью, вы можете использовать awaitable _ 1_ добавлен в .NET 4.5.

Если можно, возьму реализацию, опубликованную JoelC, и немного реорганизую ее:

private int _maxAttempts = 10;

private async Task TrySendMailAsync(int attemptNumber)   
{
     var smtpClient = new SmtpClient();
     var mailMsg = new MailMessage("[email protected]", "[email protected]", "Test Subject", "Test Body");

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

Это позволит вам выполнить цикл по запросу, но позволит основной задаче, которая является асинхронным вводом-выводом, работать без нежелательного выполнения потока пула потоков.

person Yuval Itzchakov    schedule 19.07.2014

Что-то вроде этого может решить проблему:

    private int _maxAttempts = 10;

    private void TrySendMail(int attemptNumber)

        Task.Factory.StartNew(() => SendMail("[email protected]", "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());
    }

Это не самое красивое, и вы хотите убедиться, что вы не вызываете его слишком много раз рекурсивно и получаете переполнение стека! Десять раз должно быть хорошо.

РЕДАКТИРОВАТЬ: Я изменил количество попыток на аргумент, чтобы быть более безопасным с потоковой передачей, в случае, если вы вызываете эту отправку почты, возможно, в потоке много раз.

РЕДАКТИРОВАТЬ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++;
        } 
    }
person JoelC    schedule 18.07.2014
comment
не будет ли опасно вызывать один и тот же метод сам по себе? - person Misiu; 18.07.2014
comment
Одна опасность заключается в том, что один и тот же метод вызывает слишком много раз из самого себя, что приводит к нехватке места в стеке. Я не думаю, что потоки должны вызывать проблему, потому что вы вызываете его снова после того, как закончите попытки отправить почту, так как вызов находится в ContinueWith Однако приведенный выше комментарий Мажаренко может намекнуть на более приятный способ сделать это. который содержит все в одной задаче, а не вложения задач. - person JoelC; 18.07.2014