1

I'm trying to make a Windows service that prints, but it seems like I got stuck on refreshing a PrintQueue.
It says a different thread owns the object.

Here is the error I get

at System.Windows.Threading.Dispatcher.VerifyAccess()
at System.Printing.PrintQueue.VerifyAccess()
at System.Printing.PrintQueue.Refresh()
at PrinterService.Service.<Print>d__20.MoveNext() in C:\Users\user\source\repos\PrinterService\PrinterService\Service.cs:line 237
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at PrinterService.Service.<timer_Elapsed>d__16.MoveNext() in C:\Users\user\source\repos\PrinterService\PrinterService\Service.cs:line 70
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<>c.<ThrowAsync>b__6_1(Object state)
at System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

And here is the code

public Service1()
{
    InitializeComponent();
    timer = new Timer(30*1000);        //Set time, in this case it's gonna be 30 seconds
    timer.Elapsed += timer_Elapsed;    //Add event that runs after the above set time elapsed

    // We don't want the timer to start ticking again till we tell it to.
    timer.AutoReset = false;
}

protected override void OnStart(string[] args)
{
    timer.Start();
}

private void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    new Thread(async () =>
    {
        timer.AutoReset = false;                                //Stop timer while we do our stuff
        
        List<string> pdfList = await GetPrintJobs();            //Get print jobs
        List<string> responses = await Print(pdfList);          //Print and collect responses
        if (responses.Count > 0)                                //If there is any successful prints we respond
            foreach (string response in responses)
                await Response(response, "success");
                
        timer.AutoReset = true;                                 //Start countdown when we finished doing our stuff
    }).Start();
}

private static async Task<List<string>> Print(List<string> pdfList)
{
    List<string> successfullPrints = new List<string>();

    using (LocalPrintServer printServer = new LocalPrintServer(PrintSystemDesiredAccess.AdministrateServer))
    {
        string localPrinter = printServer.DefaultPrintQueue.Name; //Default printer's name
        using (PrintQueue queue = new PrintQueue(printServer, localPrinter, PrintSystemDesiredAccess.AdministratePrinter))
        {
            while (queue.NumberOfJobs > 0)
            {
                DeletePrinterJobQueue();
                queue.Refresh();        //FIRST ERROR IS THROWN HERE
            }

            for (int i = 0; i < pdfList.Count; i++)
            {
                //Start printing
                await new PDFtoPrinterPrinter().Print(new PrintingOptions(localPrinter, pdfList[i]));
                queue.Refresh();        //ANOTHER ERROR HERE

                bool error = false;
                string reasonOfError = null;
                PrintSystemJobInfo jobInfo = queue.GetPrintJobInfoCollection().FirstOrDefault(x => x.Name.Equals(nameOfFile));

                if (jobInfo == null)
                    error = true;
                else
                {
                    while (!error && jobInfo != null)           //While the job exists AND there is no error
                    {
                        /*
                        *   ...check statuses
                        *   ...of the PrintQueue
                        */

                        queue.Refresh();    //ANOTHER ERROR HERE
                        jobInfo = queue.GetPrintJobInfoCollection().FirstOrDefault(x => x.Name.Equals(nameOfFile));     //THIS LINE THROWS THE SAME ERROR AS THE REFRESH ONE
                    }
                }

                queue.Refresh();    //ANOTHER ERROR HERE

                //if there is no error, we add the file's ID to the list, else we send an error response
                if (!error)
                    successfullPrints.Add(nameOfFile);
                else
                {
                    await Response(nameOfFile, reasonOfError);
                    break;
                }
            }
        }
    }

    return successfullPrints;
}

protected override void OnStop()
{
    timer.Stop();
}

A strange thing is sometimes the first refresh runs well, and it only throws error at the second or third one.
I think the problem has to do something with the event, maybe?
Any help would be greatly apprecieted!

  • The inconsistency is likely because the object winds up owned by the first thread where it's created, a thread pool thread, and sometimes you wind up on the same thread pool thread and sometimes not. But, a) _no_ thread pool thread should own the object -- it should be created in the UI thread, and b) you should be using `Dispatcher.Invoke()` to get back to the UI thread any time you want to use the object. Or just use async/await with `DispatcherTimer` so that you're always on the right thread in the first place. – Peter Duniho Jul 08 '21 at 17:18
  • 1
    What is the type of your application? WPF? Windows Service? In the second case, are you aware of this? *"Classes within the System.Printing namespace are not supported for use within a Windows service or ASP.NET application or service."* (from the [docs](https://learn.microsoft.com/en-us/dotnet/api/system.printing.printqueue)) – Theodor Zoulias Jul 08 '21 at 18:07
  • Btw the `Thread` constructor does not understand async delegates. You can take a look at [this](https://stackoverflow.com/questions/44364092/is-it-ok-to-use-async-with-a-threadstart-method) or [this](https://stackoverflow.com/questions/30044846/async-thread-body-loop-it-just-works-but-how). – Theodor Zoulias Jul 08 '21 at 18:12
  • 1
    @TheodorZoulias It's Windows Service and I was not aware of that. I'll try to implement it into a console application and I'll check back later – Martin Serdült Jul 08 '21 at 18:27
  • 1
    Martin OK. I'll vote to reopen the question since the [marked-as-duplicate](https://stackoverflow.com/questions/9732709/the-calling-thread-cannot-access-this-object-because-a-different-thread-owns-it) is WPF related, while this question is not. – Theodor Zoulias Jul 08 '21 at 18:33
  • @TheodorZoulias I tried it with Console application, but I get the same error :/ – Martin Serdült Jul 09 '21 at 13:13

1 Answers1

1

Since you are dealing with thread-affine components, my suggestion is to do all the work synchronously on the thread that starts the service. In other words don't use a Timer, don't use the Thread constructor, and don't use async/await. To run some work periodically on the current thread you can just perform a loop, and inject a delay inside the loop using the Thread.Sleep method:

protected override void OnStart(string[] args)
{
    while (true)
    {
        // Here do the periodic work
        Thread.Sleep(30 * 1000);
    }
}

When you have to call an asynchronous method like the PDFtoPrinterPrinter.Print, don't await it. Instead wait it synchronously by using the .GetAwaiter().GetResult() trick:

new PDFtoPrinterPrinter()
    .Print(new PrintingOptions(localPrinter, pdfList[i])).GetAwaiter().GetResult();

This is not the most sophisticated approach, but it should be enough to get the job done.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104