在进行后台操作时无法访问AbortButton

时间:2017-08-02 06:54:28

标签: c# multithreading backgroundworker

我的要求是需要在alertbox中单击中止按钮时中止后台工作操作(附加图像Export)。因为GetData()将花费更多时间来执行。

如果调用Dowork方法,则无需访问UI元素,这意味着我们需要限制它直到后台工作者完成。所以我放了Application.Current.Dispatcher。如果我删除(Application.current.dispatcher)行,我们可以访问UI元素并执行一些操作,但我们需要在执行dowork事件时限制它。

任何解决方案,

     try
        {
            var backGroundWorker = new CancelSupportedBackgroundWorker { WorkerSupportsCancellation = true };
            CancellationTokenSource source = new CancellationTokenSource();
            var alertBox = new AlertBox
            {
                IsBusy = true,
                WaitingText ="Export Data"
                WaitingHeaderText ="Exporting"
            };
            alertBox.AbortButton.Click += (obj, args) =>
            {
                source.Cancel();
                backGroundWorker.CancelAsync();
                backGroundWorker.Abort();
                backGroundWorker.Dispose();
                GC.Collect();
            };
            backGroundWorker.DoWork += (obj, args) =>                 
            {    
            Appliction.Current.Dispatcher.Invoke(DispatcherPriority.ApplicationIdle, new Action(
                delegate
                {
                    table = GetData((CancellationToken)args.Argument);

                    if (source.Token != default(CancellationToken))
                        if (source.Token.IsCancellationRequested)
                            return;
                    }));
             };
            backGroundWorker.RunWorkerCompleted += (obj, args) =>
            {
                alertBox.IsBusy = false;
            };
           backGroundWorker.RunWorkerAsync(source.Token);
        }

提前致谢。

我做了一个编辑,将一个令牌参数传递给Dowork事件

明确要求是:

1)查询操作需要在后台运行

2)我们无法访问其他UI元素,如文件菜单项

3)执行查询时,仅访问alertbox中的中止按钮

如果单击中止按钮,它将自动取消后台操作。

我使用Task.Run()方法进行了编辑

     Task backgroundTask = null;
      try
        {

            CancellationTokenSource source = new CancellationTokenSource();
            var alertBox = new AlertBox
            {
                IsBusy = true,
                WaitingText ="Export Data"
                WaitingHeaderText ="Exporting"
            };
            alertBox.AbortButton.Click += (obj, arg) =>
            {
                source.Cancel();
                GC.Collect();
            };
         backgroundTask = Task.Run(() => table =  GetFullData(source.Token));
         IWorkBook.ImportDataTable(table, true, 1, 1, true);
        }
        catch (ThreadAbortException)
        {

        }

我为添加GetFullData()方法做了一个更改

 internal DataTable GetFullData(CancellationToken token)
    {
        DataTable dataTable = new DataTable();


        if (connection.State != ConnectionState.Open)
        {
            connection.Open();
        }

        var command = connection.CreateCommand();
        command.CommandText = query;
        if (QueryParameters != null && QueryParameters.Count > 0)
        {
            foreach (var parameter in QueryParameters)
            {
                var param = command.CreateParameter();
                param.ParameterName = "@" + parameter.Name.TrimStart('@');
                if (string.IsNullOrEmpty(parameter.Value))
                {
                    param.Value = DBNull.Value;
                }
                else
                {
                    param.Value = parameter.Value;
                }

                command.Parameters.Add(param);
            }

            command.GetPreparedSql();
        }

        command.Connection = connection;
        if (connection.State != ConnectionState.Open)
        {
            connection.Open();
        }

        var reader = command.ExecuteReader();
        var dataTable = new DataTable();
       if (token != default(CancellationToken))
            token.ThrowIfCancellationRequested();
        dataTable.Load(reader);
        return dataTable;
    }

2 个答案:

答案 0 :(得分:2)

在更详细地查看您的代码之后,看起来您不需要IProgress。

如果你想在另一个线程上调用任何代码,你应该看看它,但只是基于这个问题,你只需要使用Task.Run& CancellationToken

此代码假设一个名为frmDoWork的表单,其中包含2个按钮cmdDoWorkcmdAbort

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsApplication1
{
    public partial class frmDoWork : Form
    {
        CancellationTokenSource cts = null;
        Task backgroundTask = null;

        public frmDoWork()
        {
            InitializeComponent();
        }

        private void WorkToDoInBackgroundThread(IProgress<int> progress, CancellationToken cancellationToken)
        {
            try
            {
                for (int i = 0; i < 10; i++)
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    Task.Delay(1000).Wait(cancellationToken);
                    progress.Report(i);
                    System.Diagnostics.Debug.WriteLine($"{i} - {DateTime.Now}");
                }
            }
            catch(OperationCanceledException ex)
            {

            }

        }

        private void cmdDoWork_Click(object sender, EventArgs e)
        {
            cts = new CancellationTokenSource();
            Progress <int> prg= new Progress<int>(x => this.Text = $"Iteration - {x}");

            backgroundTask = Task.Run(()=> WorkToDoInBackgroundThread(prg, cts.Token));
        }

        private void cmdAbort_Click(object sender, EventArgs e)
        {
            cts?.Cancel();
        }
    }
}

您需要检查任务是否已在运行&amp;决定做什么(取消或等待)。这实际上只是意味着如果在完成任务后将cts重置为null,则检查cts是否为null。

您也可以根据需要处理已取消的例外。这段代码就退出了。

我做了一个编辑,还展示了如何使用IProgress接口对另一个线程进行回调。

这是显示警告框时禁用所有者表单的代码段。适应您的需求。

{
    AlertBox alertBox = new AlertBox();
    alertBox.Owner = this;
    alertBox.Show();
    alertBox.Closed += (sender, e) => this.IsEnabled = true;
    this.IsEnabled = false;
}

答案 1 :(得分:0)

是的,最后我在isHitTestVisible属性的帮助下找到了解决方案,该属性用于返回某些部分的命中测试结果。

感谢@Ashley Pillay的回复。

      Window.isHitTestVisible = false;
      alertBox.AbortButton.Click += (obj, args) =>
        {
            Window.IsHitTestVisible = true;
            source.Cancel();
            backGroundWorker.CancelAsync();
            backGroundWorker.Abort();
            backGroundWorker.Dispose();
            GC.Collect();
        };
        backGroundWorker.DoWork += (obj, args) =>                 
        { 
                table = GetData((CancellationToken)args.Argument);

                if (source.Token != default(CancellationToken))
                    if (source.Token.IsCancellationRequested)
                        return;
         };
        backGroundWorker.RunWorkerCompleted += (obj, args) =>
        {
            Window.IsHitTestVisible = true;
            alertBox.IsBusy = false;
        };