Reputation: 41
I have a problem getting clipboard text via the async method. Because it always returns an empty value (while it's not empty). This is a simple demonstration of the problem:
private async void button_Click(object sender, EventArgs e)
{
string result = await Task<string>.Run(() =>
{
System.Threading.Thread.Sleep(3000);
return Clipboard.GetText(); //returns empty! (but clipboard is not empty)
});
MessageBox.Show(result);
}
I'm sure that the clipboard is not empty. what's the solution?
Upvotes: 0
Views: 765
Reputation: 4563
This should work:
private static int counter;
private async void button1_Click(object sender, EventArgs e)
{
counter++;
button1.Text = counter.ToString();
// Solution 1 (preferred)
await LongOperation();
Debug.WriteLine("Solution 1 completed for counter " + counter);
// Solution 2 (use if LongOperation is CPU-bound)
var t = Task.Run(LongOperation);
await t;
Debug.WriteLine("Solution 2 completed for counter " + counter);
Debug.WriteLine(Clipboard.GetText());
}
private async Task LongOperation()
{
await Task.Delay(10000);
}
Click 3 times in a row on button1. Result:
// Click 3 times in a row on the button. Result:
// After 10 seconds:
Solution 1 completed for counter 3
Solution 1 completed for counter 3
Solution 1 completed for counter 3
// After 10 more seconds:
Solution 2 completed for counter 3
<clipboard content>
Solution 2 completed for counter 3
<clipboard content>
Solution 2 completed for counter 3
<clipboard content>
Upvotes: 0
Reputation: 465
It doesn't work because the clipboard only works when the COM threading model (apartment)
is STA
while your Task
apartment is MTA
.
You can't change the apartment of the Task, BUT you can use Thread instead. Thread has a SetApartmentState
method.
But I've found a solution to create an STA Task !
The trick is to run an STA thread using a Task:
public static Task<T> StartSTATask<T>(Func<T> func)
{
var tcs = new TaskCompletionSource<T>();
var thread = new Thread(() =>
{
try
{
var result = func();
tcs.SetResult(result);
}
catch (Exception e)
{
tcs.SetException(e);
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
return tcs.Task;
}
So now you can make it work like this:
private async void button1_Click(object sender, EventArgs e)
{
var result = await StartSTATask(() =>
{
Thread.Sleep(3000);
return Clipboard.GetText();
});
MessageBox.Show(result);
}
Upvotes: 5