【问题标题】:Task.IsCompleted not returning correct task stateTask.IsCompleted 未返回正确的任务状态
【发布时间】:2018-12-25 11:31:41
【问题描述】:

我正在尝试在Form1_FormClosing 事件期间捕获长时间运行的任务的状态。

长时间运行的任务包括使用 HttpClient 的 async/await 调用。

当我开始运行任务时,它会循环运行直到被取消。但是,当我关闭表单且任务仍在运行时,任务状态与预期不符,并显示状态 RanToCompletionIsCompleted==true

我已经使用方法RunLongRunningMethodTest复制了这个问题

Task.Delay(2000); 工作正常,但 await Task.Delay(2000); 复制了同样的问题。由于 GetAsync 方法,我不确定如何解决 GetUrl 也必须是异步的这一事实。

如何修复下面的代码,以便 Form1_FormClosing 正确报告如果任务在运行时关闭,它仍在运行?我想检查任务状态,如果仍在运行则取消,然后等待取消完成。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private static HttpClient client { get; set; }
        private Task task { get; set; }
        private CancellationTokenSource cts { get; set; }
        private bool buttonStartStopState { get; set; }

        public Form1()
        {
            InitializeComponent();
        }

        private async void RunLongRunningMethodTest(CancellationToken cancellationToken)
        {
            try
            {
                while (true)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                    }

                    Debug.WriteLine(DateTime.Now);
                    Task.Delay(2000); // task.IsCompleted = false - Works okay
                    //await Task.Delay(2000); // task.IsCompleted = true - Not correct
                }
            }
            catch (OperationCanceledException)
            {
                // Just exit without logging. Operation cancelled by user.
            }
            catch (Exception ex)
            {
                // Report Error
            }
        }

        private async void RunLongRunningMethod(CancellationToken cancellationToken)
        {
            try
            {
                while (true)
                {
                    if (cancellationToken.IsCancellationRequested)
                    {
                        cancellationToken.ThrowIfCancellationRequested();
                    }

                    var success = await GetUrl("https://www.bbc.co.uk/");
                    Thread.Sleep(2000);
                }
            }
            catch (OperationCanceledException)
            {
                // Just exit without logging. Operation cancelled by user.
            }
            catch (Exception ex)
            {
                // Report Error
            }
        }

        private async Task<bool> GetUrl(string url)
        {
            if (client == null)
            {
                client = new HttpClient(new HttpClientHandler() { UseDefaultCredentials = true, AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip });
                client.BaseAddress = new Uri("https://www.bbc.co.uk/");
                client.DefaultRequestHeaders.Add("Accept", "*/*");
                client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
                client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; …) Gecko/20100101 Firefox/62.0");
                client.DefaultRequestHeaders.Add("Host", "https://www.bbc.co.uk/");
                client.DefaultRequestHeaders.Connection.Add("Keep-Alive");
                client.DefaultRequestHeaders.Add("DNT", "1");
            }

            var response = await client.GetAsync(url);
            var contents = await response.Content.ReadAsStringAsync();

            return true;
        }

        private void buttonStartStop_Click(object sender, EventArgs e)
        {
            buttonStartStopState = !buttonStartStopState;
            if(buttonStartStopState)
            {
                cts = new CancellationTokenSource();
                task = new Task(() => RunLongRunningMethod(cts.Token));
                task.Start();
            }
            else
            {
                cts.Cancel();
                cts = null;
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {

            if (task != null)
            {
                var taskStatus = $"{task.Status} {task.IsCompleted}";
            }
        }
    }
}

【问题讨论】:

  • 您根本不应该使用Task 构造函数,更不用说环绕async void 方法了。从长时间运行的方法中返回一个任务,等待它们的完成。只有您的事件处理程序应标记为async void

标签: c# winforms async-await


【解决方案1】:

第一个问题是您在长时间运行的方法中使用了async void。当代码到达await 行时,控制权交还给父级,并且由于任务没有其他任何东西要运行,它正确报告它已经完成。

所以,让我们解决这个问题:

private async Task RunLongRunningMethodTest
private async Task RunLongRunningMethod

第二个问题是Task构造函数的使用。不仅不鼓励使用它,而且在您的代码中也没有必要使用它。你应该这样做:

if (buttonStartStopState)
{
    cts = new CancellationTokenSource();
    task = RunLongRunningMethod(cts.Token); // will only compile after fixing the first problem
}

作为附加说明,请注意使用 Task.Delay(2000); 实际上是无用的,因为您正在创建一个被丢弃的任务。

【讨论】:

  • 一个快速的问题,因为我找不到正确的搜索词...task = RunLongRunningMethod(cts.Token);task = Task.Run(() =&gt; RunLongRunningMethod(cts.Token)); 之间有区别吗?
  • @pathDongle 是的,有很大的不同。使用Task.Run 创建一个将使用默认调度程序运行的任务,这通常意味着将创建一个新的线程池线程来处理该任务。最好的文档来自@StephenCleary,看here
  • 再次感谢,该链接包含我正在寻找的一切。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-05
  • 1970-01-01
  • 1970-01-01
  • 2017-03-14
  • 1970-01-01
相关资源
最近更新 更多