set clipboard in async method

给你一囗甜甜゛ 提交于 2020-03-05 04:16:07

问题


[STAThread]
static void Main(string[] args)
{
    DoThing().Wait();
}

static async Task DoThing()
{
    Clipboard.SetText("hi");
}

I added [STAThread] in the first place bc I got this error

ThreadStateException: Current thread must be set to single thread apartment (STA) mode before OLE calls can be made

But I am still getting the same error.

Clipboard is from System.Windows.Forms.

How do I set the clipboard from that async method?


回答1:


The issue is that async threads are run from the threadpool, and they are all MTA threads. Task.Run() also creates MTA threads.

You will have to explicitly start an STA thread to run the code. Here's a sample helper class:

public static class STATask
{
    /// <summary>
    /// Similar to Task.Run(), except this creates a task that runs on a thread
    /// in an STA apartment rather than Task's MTA apartment.
    /// </summary>
    /// <typeparam name="TResult">The return type of the task.</typeparam>
    /// <param name="function">The work to execute asynchronously.</param>
    /// <returns>A task object that represents the work queued to execute on an STA thread.</returns>

    public static Task<TResult> Run<TResult>([NotNull] Func<TResult> function)
    {
        var tcs = new TaskCompletionSource<TResult>();

        var thread = new Thread(() =>
        {
            try
            {
                // Most usages will require a message pump, which can be
                // started by calling Application.Run() at an appropriate point.

                tcs.SetResult(function());
            }

            catch (Exception e)
            {
                tcs.SetException(e);
            }
        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

        return tcs.Task;
    }

    /// <summary>
    /// Similar to Task.Run(), except this creates a task that runs on a thread
    /// in an STA apartment rather than Task's MTA apartment.
    /// </summary>
    /// <param name="action">The work to execute asynchronously.</param>
    /// <returns>A task object that represents the work queued to execute on an STA thread.</returns>

    public static Task Run([NotNull] Action action)
    {
        var tcs = new TaskCompletionSource<object>(); // Return type is irrelevant for an Action.

        var thread = new Thread(() =>
        {
            try
            {
                action();
                tcs.SetResult(null); // Irrelevant.
            }

            catch (Exception e)
            {
                tcs.SetException(e);
            }
        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();

        return tcs.Task;
    }
}

You could then implement DoThing() like this:

static async Task DoThing()
{
    await STATask.Run(() => Clipboard.SetText("hi"));
}

Note that, as pointed out by Stephen Cleary, usually you need a message pump for an STA thread. You seem to be able to get away with this if you're just setting the clipboard text, but for anything more complicated you're likely to have to run a message pump in the thread.

The easiest way to run the message pump is via a call to Application.Run(), but you will have to handle the application context yourself.



来源:https://stackoverflow.com/questions/56736803/set-clipboard-in-async-method

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!