【问题标题】:PrintQueue.Refresh() throws error stating that "The calling thread cannot access this object because a different thread owns it"PrintQueue.Refresh() 抛出错误,指出“调用线程无法访问此对象,因为不同的线程拥有它”
【发布时间】:2021-07-08 17:08:47
【问题描述】:

我正在尝试创建一个 打印Windows 服务,但似乎我在刷新 PrintQueue 时遇到了困难。
它说拥有不同的线程对象。

这是我得到的错误

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()

这是代码

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) no 线程池线程应该拥有该对象 -- 它应该在 UI 线程中创建,并且 b) 您应该在任何时候使用 Dispatcher.Invoke() 回到 UI 线程想使用对象。或者只是将 async/await 与 DispatcherTimer 一起使用,以便您始终处于正确的线程上。
  • 您的应用程序是什么类型的? WPF?视窗服务?在第二种情况下,你知道吗? “System.Printing 命名空间中的类不支持在 Windows 服务或 ASP.NET 应用程序或服务中使用。”(来自docs
  • 顺便说一句,Thread 构造函数不理解异步委托。你可以看看thisthis
  • @TheodorZoulias 这是 Windows 服务,我不知道。我会尝试将它实现到控制台应用程序中,稍后我会回来查看
  • 马丁好。由于marked-as-duplicate 与 WPF 相关,我将投票重新讨论这个问题,而这个问题与此无关。

标签: c# multithreading printing windows-services printqueue


【解决方案1】:

由于您正在处理线程仿射组件,我的建议是在启动服务的线程上同步完成所有工作。换句话说,不要使用Timer,不要使用Thread 构造函数,也不要使用async/await。要在当前线程上定期运行一些工作,您只需执行一个循环,并使用 Thread.Sleep 方法在循环内注入延迟:

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

当你必须调用像PDFtoPrinterPrinter.Print 这样的异步方法时,不要await 它。而是使用 .GetAwaiter().GetResult() 技巧同步等待它:

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

这不是最复杂的方法,但应该足以完成工作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    相关资源
    最近更新 更多