0

In a C# WinForms app, I am trying to move an image dithering operation into a background thread and add a progress bar to the form, to show the state of the processing. I have achieved all of that so far, but I have no clue how to get the resulting bytes back in the main thread.

Old Code:


interface ImageConverter {
    MCBlockVariant[,] Convert(Bitmap image, Size size, ImageDitherer ditherer);
}

private void btnConvert_Click(object sender, EventArgs e) {
    ImageDitherer d = null;

    if (cbDithering.Checked) {
        d = (ImageDitherer)cmbDitherers.SelectedValue;
        d.Reset(scaledSize);
    }

    //TODO: P1 show progress bar somewhere
    MCBlockVariant[,] blocks = imageConverter.Convert(image, scaledSize, d);            

    MCPACResultForm form = new MCPACResultForm(blocks, palette);
    form.Show();
}

I was able to successfully get this process to run in the background, by changing the the method signatures and invoking the progress bar on the main form thread, to update it throughout the process. That all works flawlessly. I just have no idea how to raise the results of that process, back to the main form thread, so I can feed the result into the new output form.

New Code:


interface ImageConverter {
    Task<MCBlockVariant[,]> Convert(Bitmap image, Size size, ImageDitherer ditherer, ProgressBar progress);
}

private async void btnConvert_ClickAsync(object sender, EventArgs e) {
    ImageDitherer d = null;

    if (cbDithering.Checked) {
        d = (ImageDitherer)cmbDitherers.SelectedValue;
        d.Reset(scaledSize);
    }

    Task<MCBlockVariant[,]> blocks = Task.FromResult(new MCBlockVariant[0, 0]);
    await Task.Run(() => {
        blocks = imageConverter.Convert(image, scaledSize, d, progressBarConvertImage);
    });

    // NOTE: This no longer works because `await` doesn't actually wait.
    // MCPACResultForm form = new MCPACResultForm(blocks.Result, palette);
    // form.Show();
}
Todd Powers
  • 127
  • 1
  • 7
  • awaut is asynchron, so it runs and lets the programm run, if ypou want to wait you need sychronous without await see https://stackoverflow.com/questions/76936889/why-does-calling-a-method-with-await-inside-not-block-parent-methods-execution – nbk Aug 19 '23 at 23:21
  • 3
    As a side-note, you could use the `Task.Run` in a cleaner way by using the generic `` version of `Task.Run`, like this: `Task blocks = await Task.Run(() => imageConverter.Convert(image, scaledSize, d, progressBarConvertImage));` – Theodor Zoulias Aug 19 '23 at 23:34
  • 1
    *`NOTE: This no longer works because await doesn't actually wait.`* -- Could you elaborate? What exactly is the problem? – Theodor Zoulias Aug 19 '23 at 23:38
  • 4
    Build a method that accepts a `Progress` delegate, then you can update a ProgressBar if needed and if possible. You just posted the declaration of an Interface that doesn't explain what the implementation is, i.e., if you can extrapolate a progress of the operations -- That call to `Task.FromResult()` makes no sense – Jimi Aug 19 '23 at 23:49
  • 2
    @TheodorZoulias when you `await` a `Task` you have the `TResult` type. `MCBlockVariant[,] blocks = await Task.Run(() => imageConverter.Convert(image, scaledSize, d, progressBarConvertImage));` – Sir Rufo Aug 20 '23 at 04:40
  • Just do `MCBlockVariant[,] blocks = await Task.Run(() => imageConverter.Convert(image, scaledSize, d, progressBarConvertImage) );` – Charlieface Aug 20 '23 at 05:24
  • @SirRufo oops, yes, you are right! – Theodor Zoulias Aug 20 '23 at 05:47
  • Do you want to do it by yourself for experimental purposes or any library would do it that can resize an image even with some low bit-per-pixel format that supports dithering and reporting progress? If the latter, you can try my drawing [library](https://github.com/koszeggy/KGySoft.Drawing) that supports all of these. There is also a WinForms [example](https://github.com/koszeggy/KGySoft.Drawing/tree/master/Examples/WinForms) that uses a progress bar, though not exactly for the resizing part (if image overlay is selected). Let me know if you need help or want me to add a specific answer. – György Kőszeg Aug 20 '23 at 11:54
  • @nbk I *want* it to run asynchronously. I just can't figure out how to get the result back to the main thread, after the process is complete. – Todd Powers Aug 20 '23 at 23:38
  • @Theodor Zoulias `imageConverter.Convert` already returns a ``. That's the only reason I included the interface code. But I did try your one-line construct and it gives me 'Cannot implicitly convert type 'MCPixelArtConverter.MCBlockVariant[*,*]' to 'System.Threading.Tasks.Task' – Todd Powers Aug 20 '23 at 23:38
  • @Theodor Zoulias _`NOTE: This no longer works because await doesn't actually wait.`_ means that when the `await Task.Run()` is called, the main thread doesn't actually wait for the task to complete, nor do I want it to. The `blocks` variable doesn't contain valid data by the time that line executes. So I commented out those two lines of original code and left myself a note to remind me of why. – Todd Powers Aug 20 '23 at 23:39
  • @Jimi You're right. The `Task.FromResult()` makes little sense. That is leftover from when I was thinking I could call those last two lines, to create and show the form. Without it, there was an 'uninitialized variable' error. The only reason I included the interface, was to illustrate that the method had been updated to return a `Task`, to satisfy the requirement of the `await` call. – Todd Powers Aug 20 '23 at 23:40
  • @Jimi Passing in a `Progress` delegate is a move in the right direction. I don't necessarily need to use it to update the `ProgressBar` (although that would be more elegant). But I might be able to pass in a delegate for `ConversionComplete(MCPixelArtConverter.MCBlockVariant[*,*] blocks)` That might give me the ability to pass the result of the asynchronous process, back to the main thread, so the result form can be shown. – Todd Powers Aug 20 '23 at 23:40
  • @György Kőszeg Thanks for the alternative, but this is actually creating a dithered image consisting of 16x16 pixel textures of Minecraft BlockStates, where average color for that BlockState is the closest to matching the pixel of the original picture. – Todd Powers Aug 20 '23 at 23:40
  • @ToddPowers Use the `Progress` for progress - guess why they name it this way. If you want to pass the result in the UI thread context use `await`, because that is the main feature of async/await pattern: continue in the same sync context as before await (here in the UI thread context). – Sir Rufo Aug 21 '23 at 04:23
  • 1
    @ToddPowers To get your **old** sync code to async you have to do 2 things: 1. add `async` => `private async void btnConvert_Click(object sender, EventArgs e)` 2. `await` the task => `MCBlockVariant[,] blocks = await Task.Run(() => imageConverter.Convert(image, scaledSize, d) );` - That is all – Sir Rufo Aug 21 '23 at 04:27
  • @Sir Rufo You set me on the right path. I had incorrectly assumed that calling `MCBlockVariant[,] blocks = _await_ Task.Run();`, would block the main thread, waiting for the return. It does *not* and I am able to interact with/resize the form while the dithering operation is underway. This is exactly what I was looking for! Thanks! The only other thing I needed to do is change the return type of the `Convert()` method from `MCBlockVariant[,]` to `Task` If you want to summarize that into a formal answer, I can mark it as correct, so you get credit. – Todd Powers Aug 21 '23 at 23:01

1 Answers1

1

You are doing this wrong. Task.FromResult is a complete non-sequitur, it makes no sense to do that before you have any result, and is in any case used to create results for synchronous functions.

Just return a value out of your lambda.

MCBlockVariant[,] blocks = await Task.Run(() => {
        imageConverter.Convert(image, scaledSize, d, progressBarConvertImage);
    });

// do stuff with result

The await means control is handed back to the calling code, and the continuation of the function is scheduled onto the ThreadPool. Be aware of reentry issues.

Charlieface
  • 52,284
  • 6
  • 19
  • 43
  • Thank you. Two of the comments on my original post led me in this direction. I was waiting for one of them to post it as an answer, so I could mark it as such. – Todd Powers Aug 26 '23 at 03:58