Конвертировать BackgroundWorker в Async - PullRequest
0 голосов
/ 27 июня 2018

Раньше я устал от использования BackgroundWorker, потому что для правильной работы требовалось так много функций. Однако, когда я перешел на C # из VB.NET (около месяца назад), я наткнулся на очень простой способ их создания;

Пример;

private void cmdMaxCompressPNG_Click(object sender, EventArgs e) {
    pbStatus.Maximum = lstFiles.Items.Count;

    List<string> FileList = Load_Listbox_Data();

    var bw = new BackgroundWorker();
    bw.WorkerReportsProgress = true;
    bw.DoWork += delegate {
        foreach (string FileName in FileList) {
            ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
            bw.ReportProgress(1);
        }
    };
    bw.ProgressChanged += (object s, ProgressChangedEventArgs ex) => {
        pbStatus.Value += 1;
    };
    bw.RunWorkerCompleted += delegate {
        lstFiles.Items.Clear();
        pbStatus.Value = 0;
        MessageBox.Show(text: "Task Complete", caption: "Status Update");
    };
    bw.RunWorkerAsync();            
}

Вот оно, все в одной функции! Легко написать, легко понять, и никакой реальной работы ног. Я даже сделал фрагмент из этого. С тех пор я преобразовал все свои многокомпонентные функции BackgroundWorker в этот маленький кусочек элегантного кода. Я также начал использовать их более свободно, чем в прошлом. Вчера я читал статью об Async и Await и о том, как, по-видимому, я должен делать вещи. У меня проблемы с обхватом головы.

Я пытался использовать локальные функции, но не могу получить правильную формулировку. Он пытается установить его синхронность.

Как бы я преобразовал вышеупомянутое в столь же жесткую реализацию логики Await / Async?

[Изменить]

ShellandWait;

private void ShellandWait(string ProcessPath, string Arguments, bool boolWait = true) {
    System.Diagnostics.Process ShellProcess = new System.Diagnostics.Process();
    ShellProcess.StartInfo.FileName = ProcessPath;
    ShellProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
    ShellProcess.StartInfo.Arguments = Arguments;
    ShellProcess.StartInfo.CreateNoWindow = true;
    ShellProcess.StartInfo.UseShellExecute = false;
    ShellProcess.StartInfo.RedirectStandardOutput = true;
    ShellProcess.Start();
    if (boolWait) { ShellProcess.WaitForExit(); }
    if (boolWait) { ShellProcess.Close(); }
}

Ответы [ 3 ]

0 голосов
/ 28 июня 2018

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

private void cmdMaxCompressPNG_Click(object sender, EventArgs e) 
{
    pbStatus.Maximum = lstFiles.Items.Count;

    var FileList = Load_Listbox_Data();

    foreach (var FileName in FileList) 
    {
        //Only thing that needs to run in the background
        await Task.Run(()=>ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
        //Back in the UI
        pbStatus.Value += 1;
    }
};
lstFiles.Items.Clear();
pbStatus.Value = 0;
MessageBox.Show(text: "Task Complete", caption: "Status Update");

Было бы еще лучше, если бы ShellandWait был изменен, чтобы он * не блокировался. Я предполагаю, что он использует Process.WaitForExit () для блокировки. Вместо этого метод должен ожидать асинхронно, прослушивая событие Exited . Такие события могут быть преобразованы в задачи, как показано в Задачи и Асинхронный шаблон на основе событий .

Метод будет выглядеть примерно так:

Task<string> ShellAsync(string commandPath,string argument)
{
    var tcs = new TaskCompletionSource<string>();
    var process = new Process();
    //Configure the process
    //...
    process.EnableRaisingEvents = true;
    process.Exited += (s,e) => tcs.TrySetResult(argument);
    process.Start();

    return tcs.Task;
}

Это позволит упростить цикл до:

foreach (var FileName in FileList) 
{
    await ShellAsync("optipng.exe", String.Format("\"{0}\"", FileName));
    //Back in the UI
    pbStatus.Value += 1;
}
0 голосов
/ 28 июня 2018

Я бы подумал сделать это с помощью Reactive Framework от Microsoft. Я думаю, что это намного мощнее, чем использование Задач.

private void cmdMaxCompressPNG_Click(object sender, EventArgs e)
{
    pbStatus.Maximum = lstFiles.Items.Count;

    var query =
        from FileName in Load_Listbox_Data().ToObservable()
        from u in Observable.Start(() => 
            System.Diagnostics.Process
                .Start("optipng.exe", String.Format("\"{0}\"", FileName))
                .WaitForExit())
        select u;

    query
        .ObserveOn(this) //marshall back to UI thread
        .Subscribe(
            x => pbStatus.Value += 1,
            () =>
            {
                lstFiles.Items.Clear();
                pbStatus.Value = 0;
                MessageBox.Show(text: "Task Complete", caption: "Status Update");
            });
}

Просто NuGet "System.Reactive.Windows.Forms" и добавьте using System.Reactive.Linq;, чтобы заставить его работать.

0 голосов
/ 27 июня 2018

Я пошел по этому пути (после прочтения еще немного), используя Task.Run ()

private async void cmdMaxCompressPNG_Click(object sender, EventArgs e) {
    pbStatus.Maximum = lstFiles.Items.Count;
    List<string> FileList = Load_Listbox_Data();
    await Task.Run(() => {
        foreach (string FileName in FileList) {
            ShellandWait("optipng.exe", String.Format("\"{0}\"", FileName));
            pbStatus.GetCurrentParent().Invoke(new MethodInvoker(delegate { pbStatus.Value += 1; }));
        }         
    });
    lstFiles.Items.Clear();
    pbStatus.Value = 0;
    MessageBox.Show(text: "Task Complete", caption: "Status Update");
}

Обратите внимание на асинхронность рядом с приватным.

Мне пришлось поработать с индикатором выполнения, так как это был индикатор состояния полосы состояния. Если бы это был стандартный элемент управления, который я мог бы использовать;

pbStatus.Invoke((Action)(() => pbStatus.Value += 1))

Ответ на индикатор выполнения находится в -> Обновить индикатор выполнения из Task.Run async

А здесь -> Как вызвать индикатор выполнения в строке состояния?

...