单击取消按钮

时间:2019-09-08 13:05:16

标签: c# wpf asynchronous mvvm async-await

在我的代码中,我单击LoadTemplated按钮从excel下载数据。 我想在用户单击“取消”按钮后立即取消下载。 任务以某种方式没有在继续进行时被取消。 在这里我需要做些什么建议。

public CheckListDetailViewModel(IAuditInspectionDataService auditInspectionDataService)
        {
            _auditInspectionDataService = auditInspectionDataService;

            LoadChecklistTemplateCommand = new DelegateCommand(OnLoadTemplate, CanLoadTemplate).ObservesProperty(() => ChecklistItems.Count);
        }

private CancellationTokenSource cts;        
        private async void OnLoadTemplate()
    {
        try
        {
            if (cts != null)
            {
                cts.Cancel();
            }
            else
            {
                cts = new CancellationTokenSource();
                IsBusy = true;
                LoadTemplateButtonValue = "Cancel";

                var items = await Task.Run(() =>
                {
                    if (cts.Token.IsCancellationRequested)
                        cts.Token.ThrowIfCancellationRequested();

                    var checklistItems = _auditInspectionDataService.GetCheckList(InspectionType.InspectionTypeId);

                    return checklistItems;

                }, cts.Token);

                LoadTemplateButtonValue = "Load Template";
                ChecklistItems = new ObservableCollection<ChecklistItem>(items);

            }


        }
        catch (Exception ex)
        {
            IsBusy = false;
            LoadTemplateButtonValue = "Load Template";
            ChecklistItems = null;
            cts = null;
            Debug.WriteLine(ex);
        }
        finally
        {
            IsBusy = false;
            LoadTemplateButtonValue = "Load Template";
            //cts.Dispose();
        }

    }

1 个答案:

答案 0 :(得分:0)

取消Task时会引发OperationCanceledException异常。捕获此异常的catch块应该是在处置已取消实例之后唯一创建CancellationTokenSource新实例的地方。 CancellationTokenSource只能使用一次(通过调用CancellationTokenSource.IsCancellationRequestedtrue设置为Cancel()之后)。

仅当调用Task并且请求取消时,才能取消CancellationToken.ThrowIfCancellationRequested()。这意味着CancellationToken的观察者负责在每次运行操作允许中止但尽可能频繁地中止CancellationToken.ThrowIfCancellationRequested()时调用,以便尽快取消请求并始终在分配资源之前取消操作。

因此,只有将当前CancellationToken的引用传递给在可取消Task执行期间所调用的每个方法才有意义。还要始终确保所有async方法都返回TaskTask<T>(事件处理程序除外,该事件处理程序需要根据void委托签名返回event) 。以下示例将按预期工作。

MainWindow.xaml

<Window>
  <Window.DataContext>
    <CheckListDetailViewModel />
  </Window.DataContext>

  <Button>
    <Button.Style>
      <Style>
        <Setter Property="Command" Value="{Binding LoadChecklistTemplateCommand}" />
        <Setter Property="Content" Value="Load Template" />

        <!-- Set the Button's Command to CancelCommand once the download has started -->
        <Style.Triggers>
          <DataTrigger Binding="{Binding IsBusy}" Value="True">
            <Setter Property="Content" Value="Cancel" />
            <Setter Property="Command" Value="{Binding CancelCommand}" />
          </DataTrigger>
        </Style.Triggers>
      </Style>
    </Button.Style>  
  </Button>
</Window>

CheckListDetailViewModel.cs

public CancellationTokenSource CancellationTokenSource { get; set; }  

private bool isBusy;
public bool IsBusy
{ 
  get => this.isBusy;
  set
  {
    this.isBusy = value; 
    OnPropertyChanged();
  }  
}

public ICommand LoadChecklistTemplateCommand { get; set; }
public ICommand CancelDownloadCommand => new DelegateCommand(CancelDownload, () => true);

public CheckListDetailViewModel(IAuditInspectionDataService auditInspectionDataService)
{
  this.CancellationTokenSource = new CancellationTokenSource();

  _auditInspectionDataService = auditInspectionDataService;
  LoadChecklistTemplateCommand = new DelegateCommand(OnLoadTemplate, CanLoadTemplate).ObservesProperty(() => ChecklistItems.Count);
}

// Cancel Task instance
public void CancelDownload()
{
  this.CancellationTokenSource.Cancel();
}

private async Task OnLoadTemplateAsync()
{
  if (this.IsBusy)
  {
    return;
  }

  CancellationToken cancellationToken = this.CancellationTokenSource.Token;
  this.IsBusy = true;

  try
  {
    var items = await Task.Run(() =>
    {
      cancellationToken.ThrowIfCancellationRequested();
      return this._auditInspectionDataService.GetCheckList(cancellationToken, InspectionType.InspectionTypeId);
    }, cancellationToken);

    this.ChecklistItems = new ObservableCollection<ChecklistItem>(items);
  }
  catch (OperationCanceledException) 
  {
    // CancellationTokenSource can only be used once. Therefore dispose and create new instance
    this.CancellationTokenSource.Dispose();
    this.CancellationTokenSource = new CancellationTokenSource();
    this.ChecklistItems = new ObservableCollection<ChecklistItem>();
  }

  this.IsBusy = false;
}

AuditInspectionDataService.cs

// Member of IAuditInspectionDataService 
public void GetCheckList(CancellationToken cancellationToken)
{ 
  using (OleDbConnection connection = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=\"myfile.xls\";Extended Properties=\"Excel 12.0;HDR=YES;IMEX=1\""))
  {
    OleDbCommand command = new OleDbCommand("SELECT * FROM [Sheet1$]", connection);

    connection.Open();
    OleDbDataReader reader = command.ExecuteReader();

    int partitionSize = 50;
    int linesRead = 0;
    while (reader.Read())
    {
      if (++linesRead == partitionSize)
      {
        cancellationToken.ThrowIfCancellationRequested();
        llnesRead = 0;
      }
      Console.WriteLine(reader[0].ToString());
    }
    reader.Close();
  }  
}