我是 C# 初學者,我正在嘗試創建一個 Windows 服務(帶有 Topshelf 的控制臺應用程式)(.Net Framwork 4.8),它每秒獲取和設定剪貼板(是的,無用的服務,它只是為了學習)。
當在我的服務類中使用 System.Windows.Forms 作為參考時,Timer 類停止作業(“'Timer' 是 'System.Windows.Forms.Timer' 和 'System.Timers.Timer' 之間的不明確參考”)和應用程式在我使用剪貼板類的行中拋出 System.Threading.ThreadStateException:“在進行 OLE 呼叫之前,必須將當前執行緒設定為單執行緒單元 (STA) 模式,確保您的 Main 函式上標記了 STAThreadAttribute” .
using System.Timers;
using System.Windows.Forms;
namespace ClipboardProject
{
public class TimerClipboard
{
private readonly Timer _timer;
public TimerClipboard()
{
_timer = new Timer(1000) { AutoReset = true };
_timer.Elapsed = TimerElapsed;
}
private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
string userClipboard = Clipboard.GetText();
Clipboard.SetText($"Latest copy: {userClipboard}");
}
public void Start()
{
_timer.Start();
}
public void Stop()
{
_timer.Stop();
}
}
}
我究竟做錯了什么?
編輯:
這是我的主要方法。
using System;
using Topshelf;
namespace ClipboardProject
{
public class Program
{
static void Main(string[] args)
{
var exitCode = HostFactory.Run(x =>
{
x.Service<TimerClipboard>(s =>
{
s.ConstructUsing(timerClipboard=> new TimerClipboard());
s.WhenStarted(timerClipboard=> timerClipboard.Start());
s.WhenStopped(timerClipboard=> timerClipboard.Stop());
});
x.RunAsLocalSystem();
x.SetServiceName("Random ServiceName");
x.SetDisplayName("Random DisplayName");
x.SetDescription("Random Description");
});
int exitCodeValue = (int)Convert.ChangeType(exitCode, exitCode.GetTypeCode());
Environment.ExitCode = exitCodeValue;
}
}
}
uj5u.com熱心網友回復:
System.Timers.Timer.Elapsed處理程式始終在后臺執行緒中運行,因此ClipboardOLE 呼叫導致System.Threading.ThreadStateException.
您可以通過創建 newSystem.Threading.Thread并強制它作為 STA 執行緒啟動來處理它SetApartmentStart(ApartmentState.STA),但這是低效、可怕和錯誤的解決方案:
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
System.Threading.Thread t = new System.Threading.Thread(() =>
{
string userClipboard = Clipboard.GetText();
Clipboard.SetText($"Latest copy: {userClipboard}");
});
t.SetApartmentState(System.Threading.ApartmentState.STA);
t.Start();
}
因為在每個間隔刻度上它會創建新的Thread. Threads 需要巨大的性能成本,特別是如果在回圈中創建。
因此,正確的解決方案可能是使用System.Windows.Threading.DispatcherTimerfromPresentationFramework庫:
public class TimerClipboard
{
private readonly System.Windows.Threading.DispatcherTimer _timer;
public TimerClipboard()
{
_timer = new System.Windows.Threading.DispatcherTimer();
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick = OnDispatcherTimerTick;
}
private void OnDispatcherTimerTick(object sender, EventArgs e)
{
string userClipboard = Clipboard.GetText();
Clipboard.SetText($"Latest copy: {userClipboard}");
}
public void Start() => _timer.Start();
public void Stop() => _timer.Stop();
}
關于“'Timer' 是System.Windows.Forms.Timer和System.Timers.Timer”之間的一個模棱兩可的參考:那是因為兩個命名空間都有 class Timer,所以您的 Studio 不知道您想要和需要使用哪個。可以通過洗掉另一個using或明確指定來解決:
using Timer = System.Timers.Timer;
// or
using Timer = System.Windows.Forms.Timer;
namespace ClipboardProject
{
// ...
}
編輯。
As @Hans Passant noticed, DispatcherTimer.Tick event couldn't fire without dispatcher, so upper solution won't work with Topshelf. So I offer to rewrite TimerClipboard class to remove any timer from there and use simple flag-based while loop. As there no timer, I renamed it to ClipboardWorker.
Complete solution looks like this:
using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using Topshelf;
namespace ClipboardProject
{
class Program
{
// Add STA attribute to Main method
[STAThread]
static void Main(string[] args)
{
var topshelfExitCode = HostFactory.Run(x =>
{
x.Service<ClipboardWorker>(s =>
{
s.ConstructUsing(cw => new ClipboardWorker());
s.WhenStarted(cw => cw.Start());
s.WhenStopped(cw => cw.Stop());
});
x.RunAsLocalSystem();
x.SetServiceName("ClipboardWorkerServiceName");
x.SetDisplayName("ClipboardWorkerDisplayName");
x.SetDescription("ClipboardWorkerDescription");
});
Environment.ExitCode = (int)topshelfExitCode;
}
}
public class ClipboardWorker
{
// Flag that would indicate our Worker in running or not
private bool _isRunning;
private int _interval = 1000; // Default value would be 1000 ms
public bool IsRunning { get => _isRunning; }
public int Interval
{
get => _interval;
// Check value which sets is greater than 0. Elseway set default 1000 ms
set => _interval = value > 0 ? value : 1000;
}
// Constructor
public ClipboardWorker()
{
Console.WriteLine();
Console.WriteLine("ClipboardWorker initialized.");
}
// "Tick" simulation.
private void DoWorkWithClipboard()
{
// Loop runs until Stop method would be called, which would set _isRunning to false
while (_isRunning)
{
Console.WriteLine(); // <--- just line break for readability
string userClipboard = Clipboard.GetText();
Console.WriteLine($"Captured from Clipboard value: {userClipboard}");
Clipboard.SetText($"Latest copy: {userClipboard}");
Console.WriteLine($"Latest copy: {userClipboard}");
// Use delay as interval between "ticks"
Task.Delay(Interval).Wait();
}
}
public void Start()
{
// Set to true so while loop in DoWorkWithClipboard method be able to run
_isRunning = true;
Console.WriteLine("ClipboardWorker started.");
// Run "ticking"
DoWorkWithClipboard();
}
public void Stop()
{
// Set to false to break while loop in DoWorkWithClipboard method
_isRunning = false;
Console.WriteLine("ClipboardWorker stopped.");
}
}
}
Sample output:

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/335733.html
