Отменить длительную задачу через 5 секунд, если она не завершена

Я создал задачу, которая создает строку XML. Задача может длиться несколько секунд. Когда задача не завершена через 5 секунд, я хочу отменить задачу «гладко» и продолжить запись остальной части XML. Поэтому я встроил отмену в свою задачу. Но хотя я вижу следующее сообщение в журнале:

  • Превышение времени ожидания ProcessInformationTask

Я также вижу эту строку в моем журнале

Добавление информации о процессе заняло 10001 мс

Интересно, почему это может произойти, потому что я хочу отменить задачу через 5 секунд (если она не завершена). Поэтому я ожидаю, что задача будет длиться максимум 5 секунд. Как я могу это решить? Возможно, отмена не настроена должным образом?

Код, в котором я вызываю свою задачу

string additionalInformation = null;
var contextInfo = new StringBuilder();

var xmlWriterSettings = new XmlWriterSettings()
{
    OmitXmlDeclaration = true,
    ConformanceLevel = ConformanceLevel.Fragment
};

var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;

using (XmlWriter xmlWriter = XmlWriter.Create(contextInfo, xmlWriterSettings))
{
    try
    {
        xmlWriter.WriteStartElement("AdditionalInformation");

        //Write xml (not long running)

        var watch = System.Diagnostics.Stopwatch.StartNew();
        string processInformation = AddProcessesInformation(xmlWriterSettings);
        watch.Stop();
        var elapsedMs = watch.ElapsedMilliseconds;
        Log.Info("Adding the process information took : " + elapsedMs + " ms");

        if (!string.IsNullOrEmpty(processInformation))
        {
            xmlWriter.WriteRaw(processInformation);
        }

        //Write xml (not long running)

        xmlWriter.WriteEndElement();

        additionalInformation = contextInfo.ToString();
    }

    catch (Exception e)
    {
        Log.Info("An exception occured during writing the additional information: " + e.Message);
        return false;
    }

    return true;
} 

Метод задачи

private static string AddProcessesInformation(XmlWriterSettings xmlWriterSettings)
    {
        var tokenSource = new CancellationTokenSource();
        var token = tokenSource.Token;
        var contextInfo = new StringBuilder();

        var processInformationTask = Task<string>.Factory.StartNew(() =>
        {
            if (token.IsCancellationRequested)
            {
                Log.Info("Cancellation request for the ProcessInformationTask");
            }

            Thread.Sleep(10000);

            return "Ran without problems'";

        }, token);

        if (!processInformationTask.Wait(5000, token))
        {
            Log.Info("ProcessInformationTask timed out");
            tokenSource.Cancel();
        }

        return processInformationTask?.Result;
    }

person Sybren    schedule 30.03.2017    source источник
comment
Вам нужно сократить это до минимально воспроизводимого примера. Нам не нужно копаться в вашем коде в поисках проблемы.   -  person rory.ap    schedule 30.03.2017
comment
Взгляните на ссылку.   -  person Krzysztof Bracha    schedule 30.03.2017
comment
Чтобы воспроизвести его, вы можете легко добавить спящий режим в AddProcessInformation.   -  person Sybren    schedule 30.03.2017
comment
@Sybren, пожалуйста, сократите свой код до минимального примера, который воспроизводит вашу проблему. Бьюсь об заклад, все эти xmlWriter обычаи не требуются   -  person Sergey Berezovskiy    schedule 30.03.2017
comment
@rory.ap смотрите мой отредактированный код   -  person Sybren    schedule 30.03.2017
comment
@SergeyBerezovskiy посмотри мой отредактированный код   -  person Sybren    schedule 30.03.2017


Ответы (1)


Я думаю, вы должны проверять, запрашивается ли отмена несколько раз после каждого шага в вашем методе. Вот пример использования цикла for:

    private static string AddProcessesInformation(XmlWriterSettings xmlWriterSettings)
    {
        var tokenSource = new CancellationTokenSource();
        var token = tokenSource.Token;
        var contextInfo = new StringBuilder();

        var processInformationTask = Task<string>.Factory.StartNew(() =>
        {               
            for(int i = 0; i < 10; i++)
            {
                if (token.IsCancellationRequested)
                {
                    Console.WriteLine("Cancellation request for the ProcessInformationTask");
                    return string.Empty;
                }
                Thread.Sleep(1000);
            }

            return "Ran without problems'";

        }, token);

        if (!processInformationTask.Wait(5000, token))
        {
            Console.WriteLine("ProcessInformationTask timed out");
            tokenSource.Cancel();
        }

        return processInformationTask?.Result;
    }

Если внутри метода задачи вы вызываете другие методы, вы можете передать им токен отмены и использовать метод token.ThrowIfCancellationRequested() внутри.

        var processInformationTask = Task<string>.Factory.StartNew(() =>
        {
            try
            {
                Method1(token);
                Thread.Sleep(1000);
                Method2(token);
                Thread.Sleep(1000);
            }
            catch(OperationCanceledException ex)
            {
                return string.Empty;
            }
            return "Ran without problems'";

        }, token);


    private static void Method1(CancellationToken token)
    {
        token.ThrowIfCancellationRequested();
        // do more work
    }
person daniell89    schedule 30.03.2017
comment
Обновлено. Проверь это сейчас. - person daniell89; 30.03.2017