Возникла исключительная ситуация типа «System.Threading.Tasks.TaskCanceledException», но она не была обработана в коде пользователя. - PullRequest
0 голосов
/ 05 февраля 2020

Я получаю эту ошибку при попытке отменить и перезапустить Task, используя этот в качестве руководства. Для меня неясно, почему ошибка возникает несмотря на попытку / ловлю.

Что должно произойти

Код предназначен для запуска задачи поиска после Пользователь вводит значение в текстовое поле с задержкой в ​​1 секунду. Однако, если пользователь продолжит печатать после задержки, я бы хотел отменить задачу и перезапустить ее.

Код в главном окне (PMSearch) для запуска задачи

Запуск задачи работает. Вот код:

    CancellationTokenSource CancellationSource;
    Task ActiveTask;

    private void SearchTermEntered(object sender, string searchText)
    {
         ...do some stuff
         _ = SearchAsync();
    }

    private async Task SearchAsync()
    {
        CancelSearch(null,null);
        CancellationSource = new CancellationTokenSource();
        SearchManager sMan = new SearchManager();
        try
        {
            var searchTask = sMan.SearchPDFAsync(SearchTerm, CancellationSource.Token);
            System.Diagnostics.Debug.WriteLine("Starting Task");
            var results = await searchTask;
            System.Diagnostics.Debug.WriteLine("Results: " + results.ToString());
        }
        catch (TaskCanceledException ex)
        {
            System.Diagnostics.Debug.WriteLine("Cancelled: " + ex.Task.ToString());
        }
    }

Код для отмены задачи

Это обработчик для отмены задачи.

    private void CancelSearch(object sender, EventArgs e)
    {
        //Called when user continues to enter text.
        CancellationSource?.Cancel();
    }

Asyn c Код, запускаемый задачей

    public Task<decimal> SearchPDFAsync(string searchTerm, CancellationToken cancellationToken)
    {
        Task<decimal> task = null;
        task = Task.Run(() =>
        {

            decimal result = 0;

            // Loop for a defined number of iterations
            for (int i = 0; i < 1000; i++)
            {
                // HERE IS WHERE I'M TOLD THERE IS AN UNHANDLED EXCEPTION
                if (cancellationToken.IsCancellationRequested) throw new TaskCanceledException(task);

                // Do something that takes times like a Thread.Sleep in .NET Core 2.
                Thread.Sleep(10);
                result += i;
            }

            return result;
        });

        return task;
    }

Ошибка

Я получаю ошибку в SearchPDFAsync при: if (cancellationToken.IsCancellationRequested) throw new TaskCanceledException(task); Насколько я понимаю, исключением является правильный шаблон дизайна. Неясно, почему исключение «необработано», когда код находится внутри try / catch в PMSearch.SearchAsync().

Что я делаю не так?

Редактировать 1 Для ясности я объединил код в один класс.

Обратите внимание на несколько изменений - я преобразовал SearchTermEntered в асинхронный c метод.

   private async void SearchTermEntered(object sender, string searchText)
    {
        //Refreshes the topics listbox as new values are entered
        SearchTerm = searchText.ToLower();

        stkPnlTopics.Visibility = Visibility.Collapsed;
        stkPnlMetrics.Visibility = Visibility.Collapsed;
        stkPnlResources.Visibility = Visibility.Collapsed;

        if (SearchTerm.Length <= 2) return;
        if (string.IsNullOrEmpty(SearchTerm))
        {
            stkPnlTopics.Visibility = Visibility.Collapsed;
            txtBlkTopics.Text = "";
        }
        else if (SearchTerm.IsNumeric() || GLSearchPatterns.Contains(SearchTerm))
        { 
            //user is searching for a guideline
            txtBlkTopics.Text = "Guidelines";
            stkPnlTopics.Visibility = Visibility.Visible;
        }
        else
        {
            await SearchAsync();
        }
        CollectionViewSource.GetDefaultView(lstBxTopics.ItemsSource).Refresh();
    }

Два изменения здесь:

  • Я положил для l oop внутрь SearchAsync
  • По совету, приведенному ниже, я изменил проверку отмены на ThrowIfCancellationRequested, что означает для получения OperactionCanceledException

    private async Task SearchAsync()
    {
        CancellationSource = new CancellationTokenSource();
        try
        {
            System.Diagnostics.Debug.WriteLine("Starting Task");
            await Task.Run(() =>
            {
                decimal result = 0;
                // Loop for a defined number of iterations
                for (int i = 0; i < 100; i++)
                {
                    // Check if a cancellation is requested, if yes,
                    // throw a TaskCanceledException.
                    CancellationSource.Token.ThrowIfCancellationRequested();
    
                    // Do something that takes times like a Thread.Sleep in .NET Core 2.
                    Thread.Sleep(10);
                    result += i;
                }
    
                System.Diagnostics.Debug.WriteLine("Result: " + result.ToString());
            });
        }
        catch (OperationCanceledException)
        {
            System.Diagnostics.Debug.WriteLine("Op cancelled exception.");
        }
    }
    
    private void CancelSearch(object sender, EventArgs e)
    {
        System.Diagnostics.Debug.WriteLine("Cancel Search");
        CancellationSource?.Cancel();
    }
    

Даже в этом формате проблема сохраняется.

Ответы [ 3 ]

0 голосов
/ 05 февраля 2020

Попробуйте использовать cancellationToken.ThrowIfCancellationRequested (); РЕДАКТИРОВАТЬ: Извините, попробуйте сделать это без использования Task.Run ();

        public async Task<decimal> SearchPDFAsync(string searchTerm, CancellationToken cancellationToken) {

            decimal result = 0;
            await Task.Yield();

            for(int i = 0; i < 1000; i++) {
                cancellationToken.ThrowIfCancellationRequested();

                Thread.Sleep(10);
                result += i;
            }

            return result;
        }

await Task.Yield (); вернет управление из метода и поставит его в очередь для последующего выполнения. Мог бы работать

0 голосов
/ 05 февраля 2020

Согласно ответу Майкла Рэндалла, я был вынужден ждать всего. Это некрасиво, но работает.

Ключ меняется:

  • SearchTermEntered теперь обработчик asyn c
  • SearchAsync() вызывается с await
  • Task.Run вызывается с помощью await и его анонимная функция вызывается с помощью asyn c

        private async void SearchTermEntered(object sender, string searchText)
        {
            //Refreshes the topics listbox as new values are entered
            SearchTerm = searchText.ToLower();
    
            stkPnlTopics.Visibility = Visibility.Collapsed;
            stkPnlMetrics.Visibility = Visibility.Collapsed;
            stkPnlResources.Visibility = Visibility.Collapsed;
    
            if (SearchTerm.Length <= 2) return;
            if (string.IsNullOrEmpty(SearchTerm))
            {
                stkPnlTopics.Visibility = Visibility.Collapsed;
                txtBlkTopics.Text = "";
            }
            else if (SearchTerm.IsNumeric() || GLSearchPatterns.Contains(SearchTerm))
            { 
                //user is searching for a guideline
                txtBlkTopics.Text = "Guidelines";
                stkPnlTopics.Visibility = Visibility.Visible;
            }
            else
            {
                await SearchAsync();
            }
    
            CollectionViewSource.GetDefaultView(lstBxTopics.ItemsSource).Refresh();
        }
    
        /// <summary>
        /// https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming
        /// </summary>
        private async Task SearchAsync()
        {
            CancellationSource = new CancellationTokenSource();
            try
            {
                //SearchManager sMan = new SearchManager();
                //System.Diagnostics.Debug.WriteLine("Starting Task");
                //var results = await sMan.GetArtifactsWithSearchValAsync(SearchTerm, CancellationSource.Token);
                //System.Diagnostics.Debug.WriteLine("Results: " + results.ToString());
    
                System.Diagnostics.Debug.WriteLine("Starting Task");
                await Task.Run(async () =>
                {
                    decimal result = 0;
                    // Loop for a defined number of iterations
                    for (int i = 0; i < 100; i++)
                    {
                        // Check if a cancellation is requested, if yes,
                        // throw a TaskCanceledException.
                        CancellationSource.Token.ThrowIfCancellationRequested();
    
                        // Do something that takes times like a Thread.Sleep in .NET Core 2.
                        //Thread.Sleep(10);
                        await Task.Delay(10);
                        result += i;
                    }
    
                    System.Diagnostics.Debug.WriteLine("Result: " + result.ToString());
                });
            }
            catch (OperationCanceledException)
            {
                System.Diagnostics.Debug.WriteLine("Op cancelled exception.");
            }
        }
    
        private void CancelSearch(object sender, EventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Cancel Search");
            CancellationSource?.Cancel();
        }
    
0 голосов
/ 05 февраля 2020

Обновление

Примечание : вопрос был значительно обновлен с момента получения ответа

Когда При исключении задачи существует небольшая разница в том, как обрабатываются исключения .

См. Блог Стивена Клири о Eliding Asyn c и Await

Лучший способ - просто сделать все это async. Это позволит поместить исключение в задачу и, в свою очередь, быть брошенным и быть пойманным там, где вы ожидаете

public async Task<decimal> SearchPDFAsync(string searchTerm, CancellationToken cancellationToken)
{

    return await Task.Run(() =>
    {

        decimal result = 0;

        // Loop for a defined number of iterations
        for (int i = 0; i < 1000; i++)
        {
            // HERE IS WHERE I'M TOLD THERE IS AN UNHANDLED EXCEPTION
            if (cancellationToken.IsCancellationRequested) throw new TaskCanceledException(task);

            // since we are async we can use Task.Delay instead of Thread.Sleep 
            await Task.Delay(10);
            result += i;
        }

        return result;
    });
}

Однако, в зависимости от вашей рабочей нагрузки (и от того, связан ли это с процессором), это, вероятно, просто должен быть sync и task, запускаемый при вызове метода .

См. Блог Стивена Клири на Примеры этикета Task.Run: Не используйте Task.Run в реализации

Другой (менее красноречивый) Решение состоит в том, чтобы перехватить исключение и поместить его в задачу , при этом будет выдано исключение , когда метод ожидается , и более подробно следует тому, что сгенерированный IAsyncStateMachine компилятор будет реализовывать при использовании asyn c и шаблона ожидания

public Task<decimal> SearchPDFAsync(string searchTerm, CancellationToken cancellationToken)
{
   try
   {

      return Task.Run(() => {
         ...
      });

   }
   catch (Exception ex)
   {
      return Task.FromException<decimal>(ex);
   }
}

И, наконец, вам может не потребоваться throw внутри задачи вообще и просто вернуть что-то, что задача отменена .


Обновление 2

Если вы создаете окно автозаполнения, все в порядке, хотя вы также можете посмотреть на Reactive Extensions (RX) , у них есть некоторые отличные расширения для осуждения и регулирования, и многое другое.


Оригинал

Его не ловит, потому что Exception является ненаблюдаемым из-за вашего Task.Run. Подумайте об этом, вы просите кого-то выбраться и выполнить задачу , затем уходите и задаетесь вопросом, почему (если у них есть проблема) вы не знаете об этом

task = Task.Run(() =>

Примечание : я неправильно понял код.

...