Search code examples
c#asynchronousclipboard

How can I put text in Clipboard from within an async method in C#?


In a C# console app (.net framework 4.7), I am able to copy text to the clipboad like so:

using System.Windows.Forms;

class Program {
    [STAThread]
    public static void Main(string[] args] {
        Clipboard.SetText("this works");
    }
}

But my logic requires Main to be async because I will be calling other async methods from main. Once I change it to async, I keep getting an exception everytime I try to access the clipboard.

using System.Windows.Forms;

class Program {
    
    [STAThread]
    public static async Task Main(string[] args] {
        string text = await GetTextAsync();

        // this throws System.Threading.ThreadStateException

        Clipboard.SetText(text);

        await Task.Run(()=>Clipboard.SetText("this throws too"));

        await Task.Run([STAThread]()=>Clipboard.SetText("this doesn't compile"));
    }

    [STAThread]  // this attribute doesn't change anything
    public static async Task<string> GetTextAsync() {
        // fetching data from database that takes too much time
        // for simplicity, assume that the string value is ready
        // after one second
        await Task.Delay(1000);
        return "value fetched from database"; 
    }
}

How can I put text in the clipboard from within an Async method?

EDIT

I already tried the suggested answer from comments: https://stackoverflow.com/a/56737049/14171304

However, the code won't compile because of [NotNull] attribute not being recognized. When I remove it, There is another problem preventing compilation:

The type arguments for method STATask.Run(Func) cannot be inferred from the usage. Try specifying the type arguments explicitly.


Solution

  • I got several suggestions from the comments to my question (by @dr.null and @Dai), and I finally managed to have a working solution to my problem.

    The answer in set clipboard in async method contained 2 solutions. Only the second one was working for me. I was about to close or delete the question, but decided to post this answer to remove any confusion caused by the answer in that question.

    public static class STATask {
        public static Task Run(Action action) {
            var tcs = new TaskCompletionSource<ojbect>();
            var thread = new Thread(() => {
                try {
                    action();
                    tcs.SetResult(null);
                } catch (Exception e) {
                    tcs.SetException(e);
                }
                thread.SetApartmentState(ApartmentState.STA);
                thread.Start();
                
                return tcs.Task;
    
            });
        }
    }
    

    Now from my Main method, I simply utilize the STATask by:

    public static async Task Main() {
            string text = await GetTextAsync();
            await STATask.Run(() => Clipboard.SetText(text));
    }
    

    The solution above doesn't even require to use the attribute [STAThread] anywhere.