user11448245
user11448245

Reputation: 41

Get clipboard text in async method

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

Answers (2)

evilmandarine
evilmandarine

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

Mohammad-R
Mohammad-R

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.

STA and MTA explained here

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

Related Questions