-1

I'm trying to print document pages using System.Drawing.Printing.PrintDocument OnPrintPage method.

I need to call and external service to get measurements needed to print current page. When I call the API, graphics objects seems to be locked because of multithreading(?) and throws exception when I try to use it after acquiring the data from API. Can't get the data before printing the document. Is there any way to avoid such blocking?

    protected override async void OnPrintPage(PrintPageEventArgs e)
    {
        var location = await foo.CallWebAPI();

        //Do something with Graphics 
        e.Graphics.DrawLine(....);
        //ERROR - Argument Exception
        
    }

EDIT:

I forgot to add async in signature, but that's not part of my question. Graphics object in debugger seems to be locked/disposed after awaited call. It shows ArgumentException on any of graphics fields. Managed to solve it by using Result instead of awaiting call, but I wonder if there is any better solution.

var location = foo.CallWebAPI().Result;
Tazi
  • 435
  • 5
  • 9
  • 1
    The event handler should be defined as `async` in order for you to be able to use the `await` keyword inside it: `protected override async void OnPrintPage(PrintPageEventArgs e) { ... }`. But how is `foo` related to `location` in your sample code...? What exception are you getting? – mm8 Apr 07 '21 at 14:56
  • Please add message and stacktrace of the exception as text to your question. – Fildor Apr 07 '21 at 15:09
  • `but I wonder if there is any better solution` - it is [not really a solution](https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html). – GSerg Apr 07 '21 at 17:26

2 Answers2

2

No, you cannot do that.

You can get it to compile by adding async to your handler signature, but you won't be able to process the event meaningfully.

When your async event handler encounters its first incomplete await, the execution is returned to the caller (the object that raised the event). At that point the event source is certain that you have handled the event completely, so it can proceed to do other things, including cleaning up the Graphics object it allocated before raising the event.

When you finally get around to use that Graphics object, not only has it already been disposed by the event source, but the event source is most likely not in a state where your event-handling actions would be appropriate.

You can only use an async event handler when it does not depend on what the event source will do after its event has been handled.

GSerg
  • 76,472
  • 17
  • 159
  • 346
  • Capture any data you depend on from the `PrintPageEventArgs` synchronously *before* you call `await ...` and return from the method. – mm8 Apr 07 '21 at 15:34
  • @mm8 How would capturing the `e.Graphics` look (cloning I assume?), and how would you then use it given that the `PrintDocument` has completed its printing workflow? In `OnPrintPage` you are supposed to have code that actually draws things on the canvas. – GSerg Apr 07 '21 at 15:36
  • I don't know about `e.Graphics` itself but I guess `e.Graphics.VisibleClipBounds.X` is a primitive value type. Anyway, my point is that you can handle the event synchronously as usual and then call the remote API. – mm8 Apr 07 '21 at 15:37
  • 1
    @mm8 I put a missleading example, sorry(edited question). I want to draw something after calling an API based on the result. So I need fully functional graphics object. – Tazi Apr 07 '21 at 15:52
  • So (again) why don't you access the event args synchronously before you call your async method? – mm8 Apr 07 '21 at 17:49
1

The event handler must be defined as async in order for you to be able to use the await keyword inside it:

protected override async void OnPrintPage(PrintPageEventArgs e) { ... }. 

Regarding async methods with a return type of void, the guideline is to avoid async void except when used in an event handler so using async void in an event handler is just fine:

Should I avoid 'async void' event handlers?

It might also be an idea to capture any state from the PrintPageEventArgs before you await the async method and return from the event handler:

protected override async void OnPrintPage(PrintPageEventArgs e)
{
    //1. Capture the state
    var foo = e.Graphics.VisibleClipBounds.X;

    //2. Call async method
    var location = await foo.CallWebAPI();
}
mm8
  • 163,881
  • 10
  • 57
  • 88