{"record":{"id":"644371b322e9c163","repo":"dotnet/wpf","slug":"sr-ptprovider-verifyaccess","errorCode":null,"errorMessage":"SR.PTProvider_VerifyAccess","messagePattern":"SR\\.PTProvider_VerifyAccess","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/ReachFramework/PrintConfig/PTProvider.cs","lineNumber":558,"sourceCode":"                _deviceName = null;\n                _schemaVersion = 0;\n                _thread = null;\n            }\n\n            _disposed = true;\n        }\n\n        #endregion Dispose Pattern\n\n        #region Private Methods\n\n        private void VerifyAccess()\n        {\n            ObjectDisposedException.ThrowIf(_providerHandle is null, typeof(PTProvider));\n\n            if(_thread != Thread.CurrentThread)\n            {\n                throw new InvalidOperationException(SR.PTProvider_VerifyAccess);\n            }\n        }\n        \n        /// <summary>\n        /// Copies the managed source stream data to a native buffer and exposes the native buffer as an unmanaged IStream\n        /// </summary>\n        /// <remarks>\n        /// This method reads the stream from its current cursor to the end.\n        /// Caller is responsible for freeing the native buffer created (by using Marshal.ReleaseComObject on the IStream).\n        /// The input memory stream is expected to have a publicly visible byte buffer\n        /// </remarks>\n        /// <param name=\"stream\">the source MemoryStream</param>\n        /// <returns>IStream copy of the input stream</returns>\n        private static IStream IStreamFromMemoryStream(MemoryStream stream)\n        {\n            if (stream == null)\n            {\n                return null;","sourceCodeStart":540,"sourceCodeEnd":576,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/ReachFramework/PrintConfig/PTProvider.cs#L540-L576","documentation":"PTProvider is an STA-affine object bound to the thread that created it; VerifyAccess throws InvalidOperationException when a method is called from a different thread. The provider also throws if it has already been disposed. GetPrintCapabilities, MergeAndValidatePrintTicket, ConvertDevModeToPrintTicket and ConvertPrintTicketToDevMode all funnel through this check.","triggerScenarios":"Calling any PTProvider public method (directly or via PrintQueue/PrintTicket APIs) from a background/worker thread different from the one that created the provider, or calling after Release()/dispose.","commonSituations":"Printing work moved to a Task.Run/ThreadPool thread while the PrintQueue/PrintTicket was created on the UI thread; async printing continuations on a different thread; reusing a disposed provider instance.","solutions":["Create and use the PTProvider/PrintQueue/PrintTicket on the same thread, or marshal the call back to the creating thread (Dispatcher.Invoke).","Create a new PTProvider instance on the thread where it will be used.","Check/ensure the provider has not been disposed before calling.","Catch InvalidOperationException and re-create the provider for that thread."],"exampleFix":"// before\nTask.Run(() => queue.GetPrintCapabilities(ticket)); // wrong thread\n// after\nDispatcher.Invoke(() => queue.GetPrintCapabilities(ticket)); // same thread that created the queue","handlingStrategy":"try-catch","validationCode":"if (provider == null || provider._providerHandle == null) throw new ObjectDisposedException(nameof(PTProvider));\n// capture the creating thread at construction and compare before calling","typeGuard":"bool CanUseProvider(PTProvider p, Thread creatorThread) => p != null && Thread.CurrentThread == creatorThread;","tryCatchPattern":"try\n{\n    var caps = queue.GetPrintCapabilities(ticket);\n}\ncatch (InvalidOperationException)\n{\n    // wrong thread or disposed: recreate on current thread\n    queue = new PrintQueue(server, queueName);\n    caps = queue.GetPrintCapabilities();\n}","preventionTips":["Create and use PrintQueue/PrintTicket/PTProvider on one thread (typically the UI thread)","Use Dispatcher.Invoke for UI-thread-bound print objects","Don't share PTProvider instances across threads; create one per thread","Release the provider deterministically with using/Dispose and never reuse it after"],"tags":["wpf","printing","threading","sta","invalidoperation"],"backgroundTag":"invalid-state-transition","analyzedSha":"81131a70a4c573cd62748a5c36908fc4d662daa9","analyzedAt":"2026-09-14T10:12:48.479Z","contentChangedAt":"2026-09-14T10:12:48.479Z","schemaVersion":2},"datasetVersion":"2026-09-21T21:30:21.729Z"}