Отмена фонового работника сразу же в середине операции - PullRequest
0 голосов
/ 21 сентября 2019

Я пытаюсь понять, что я могу и не могу сделать с фоновыми работниками.Я видел довольно много сообщений по этому вопросу, но все это, кажется, связано с какой-то операцией с циклами, и вы отменяете операцию внутри цикла.Я хочу выяснить, могу ли я отменить некоторую операцию с фоновым рабочим без цикла.
У меня есть следующая простая форма, с которой я играю:

enter image description here

, который содержит следующий код:

string[,] TestData = new string[300000, 100];
List<string> TestDataList;
private static Random random = new Random();

public Form1()
{
    InitializeComponent();
    // Loading up some fake data
    for (int i = 0; i < 300000; i++)
    {
        for (int j = 0; j < 100; j++)
        {
            this.TestData[i, j] = RandomString(10) + j.ToString();
        }
    }
}
public static string RandomString(int length)
{
    const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    return new string(Enumerable.Repeat(chars, length)
      .Select(s => s[random.Next(s.Length)]).ToArray());
}

, который загружает массив строк с большим количеством фиктивных данных.Метод кнопки запуска выглядит следующим образом:

private void StartWork_Click(object sender, EventArgs e)
{
    try
    {
        System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
        BackgroundWorker bw = new BackgroundWorker();
        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_Complete);
        bw.RunWorkerAsync();
    }
    catch (Exception ex)
    {
        MessageBox.Show("Something went wrong.\nError:" + ex.Message);
    }
}

И у меня также есть:

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    this.TestDataList = this.TestData.Cast<string>()
    .Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
    .GroupBy(g => g.GroupIndex)
    .Select(g => string.Join(",", g.Select(x => x.Item))).ToList();

}

private void bw_Complete(object sender, RunWorkerCompletedEventArgs e)
{
    this.showWorkingLabel.Text = "Work done";
}

private void btnCancel_Click(object sender, EventArgs e)
{
    // I want to cancel the work with this button


    // Then show
    this.showWorkingLabel.Text = "Work Cancelled";
}

Итак, вы заметите, что мой метод bw_DoWork не содержит никаких циклов, а толькоодна операция, и я хочу знать, если:

  1. Если я могу убить / отменить фонового работника, нажав кнопку Отмена во время выполнения следующего кода:
    .Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
    .GroupBy(g => g.GroupIndex)
    .Select(g => string.Join(",", g.Select(x => x.Item))).ToList();
Могу ли я обновить метку showWorkingLabel во время фоновой работы, чтобы она постоянно отображала ".", "..", "...", а затем вернуться к ".", как индикатор выполнения, чтобы указать, что работа все еще продолжается

Ответы [ 3 ]

1 голос
/ 21 сентября 2019

Сначала вам нужно поддержать отмену

bw.WorkerSupportsCancellation = true;

Затем вам нужно поделиться токеном отмены на уровне формы

CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken cancellationToken;

Внутри вашей работы вам нужно выбросить отмену:

cancellationToken.ThrowIfCancellationRequested();

Или обработайте его корректно с фоновым работником даже для ожидающих отмен: BackgroundWorker.CancellationPending

А в кнопке отмены вызова вы можете вызвать отмену следующим образом:

cts.Cancel();

Используя ваш код, он станет похож на следующий указатель, вы должны обрабатывать изящные отмены :

    string[,] TestData = new string[30000, 100];
    List<string> TestDataList;
    private static Random random = new Random();
    CancellationTokenSource cts = new CancellationTokenSource();
    CancellationToken cancellationToken;

    private void BtnStart_Click(object sender, EventArgs e)
    {
        try
        {
            this.showWorkingLabel.Text = "Work start";
            System.Threading.SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
            BackgroundWorker bw = new BackgroundWorker();
            bw.WorkerSupportsCancellation = true;
            bw.DoWork += new DoWorkEventHandler(bw_DoWork);

            cancellationToken = cts.Token;
            cancellationToken.Register(bw.CancelAsync);

            bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_Complete);
            bw.RunWorkerAsync();

        }
        catch (Exception ex)
        {
            MessageBox.Show("Something went wrong.\nError:" + ex.Message);
        }
    }

    private void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        cancellationToken.ThrowIfCancellationRequested();
        this.TestDataList = this.TestData
            .Cast<string>()
            .Select((s, i) => new { GroupIndex = i / 100, Item = s.Trim().ToLower() })
            .GroupBy(g => g.GroupIndex)
            .Select(g =>
            {
                cancellationToken.ThrowIfCancellationRequested();
                return string.Join(",", g.Select(x => x.Item));
            })
            .ToList();
    }

    private void btnCancel_Click(object sender, EventArgs e)
    {
        cts.Cancel();

        this.showWorkingLabel.Text = "Work Cancelled";
    }
1 голос
/ 21 сентября 2019

Согласно странице MSDN для BackgroundWorker :

При создании работника вы можете настроить поддержку отмены, установив

backgroundWorker.WorkerSupportsCancellation = true;

Вы можете запросить отмену с помощьювызов CancelAsync() для BackgroundWorker.

Тогда ваш BackgroundWorker должен периодически проверять свойство BackgroundWorker.CancellationPending, а если установлено значение true, он должен отменить свою работу.Как отметил сэр Руфо в комментарии, не забывайте, что внутри делегата DoWork необходимо установить DoWorkEventArgs.Cancel на true.

На странице MSDN, на которую я ссылался, есть дополнительные примеры использования в реальных условиях.код.

0 голосов
/ 21 сентября 2019

Вот рабочий пример, использующий встроенную поддержку отмены BackgroundWorker.

// We need to remember the BackgroundWorker
private BackgroundWorker bw;

private void StartWork_Click( object sender, EventArgs e )
{
    bw = new BackgroundWorker
    {
        WorkerSupportsCancellation = true,
    };

    bw.DoWork += Bw_DoWork;
    bw.RunWorkerCompleted += Bw_RunWorkerCompleted;
    bw.RunWorkerAsync();

    showWorkingLabel.Text = "Work started ...";
}

private void Bw_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
{
    if ( e.Cancelled ) // was it cancelled?
    {
        showWorkingLabel.Text = "Work cancelled.";
        return;
    }

    if ( e.Error != null ) // any error?
    {
        showWorkingLabel.Text = "Work faulted - " + e.Error.Message;
        return;
    }
    // assign the bw Result to the field
    this.TestDataList = (List<string>)e.Result;
    showWorkingLabel.Text = "Work completed.";
}

private void Bw_DoWork( object sender, DoWorkEventArgs e )
{
    try
    {
        e.Result = this.TestData
            .Cast<string>()
            .Select( ( s, i ) =>
            {
                // check for cancellation
                if ( bw.CancellationPending )
                    throw new OperationCanceledException();
                return new
                {
                    GroupIndex = i / 100,
                    Item = s.Trim().ToLower()
                };
            } )
            .GroupBy( g => g.GroupIndex )
            .Select( g =>
            {
                // check for cancellation
                if ( bw.CancellationPending )
                    throw new OperationCanceledException();
                return string.Join( ",", g.Select( x => x.Item ) );
            } )
            .ToList();
    }
    catch ( OperationCanceledException )
    {
        e.Cancel = true;
    }
}

private void btnCancel_Click( object sender, EventArgs e )
{
    // request cancellation
    bw.CancelAsync();
    showWorkingLabel.Text = "Work cancellation requested ...";
}

и другой, который делает то же самое с современными async / await Task и CancellationToken

private CancellationTokenSource cts;

private async void StartWork_Click( object sender, EventArgs e )
{
    showWorkingLabel.Text = "Work started ...";
    cts = new CancellationTokenSource();
    var token = cts.Token;

    try
    {
        TestDataList = await Task.Run( () =>
        {
            return this.TestData
                .Cast<string>()
                .Select( ( s, i ) =>
                {
                    token.ThrowIfCancellationRequested();
                    return new
                    {
                        GroupIndex = i / 100,
                        Item = s.Trim().ToLower()
                    };
                } )
                .GroupBy( g => g.GroupIndex )
                .Select( g =>
                {
                    token.ThrowIfCancellationRequested();
                    return string.Join( ",", g.Select( x => x.Item ) );
                } )
                .ToList();
        }, token );
        showWorkingLabel.Text = "Work completed.";
    }
    catch ( OperationCanceledException )
    {
        showWorkingLabel.Text = "Work canceled.";
    }
    catch ( Exception ex )
    {
        showWorkingLabel.Text = "Work faulted - " + ex.Message;
    }

}

private void btnCancel_Click( object sender, EventArgs e )
{
    cts.Cancel();
    showWorkingLabel.Text = "Work cancellation requested ...";
}
...