【问题标题】:Continuing code on another thread在另一个线程上继续代码
【发布时间】:2015-12-19 02:39:02
【问题描述】:

我正在编写一些在 UI 线程上调用的代码,在另一个线程(不是 ThreadPool,但每次都是同一个线程)上调用一些代码,然后在 UI 线程上恢复。我想要一些关于最好的异步方式的建议。

EnsureThread 方法的复杂性是因为其他线程每次都必须是同一个线程,并且必须是运行 Dispatcher 的 STA。这是因为我需要使用 MCI,但不希望它在 UI 线程上运行。见这里https://stackoverflow.com/a/32711239/420159

我这样创建第二个线程:

private static void EnsureThread()
{
    if (eventLoopThread != null)
    {
        return;
    }

    lock (eventLoopLock)
    {
        if (eventLoopThread == null)
        {
            var lck = new EventWaitHandle(false, EventResetMode.ManualReset);

            var t = new Thread(() =>
            {
                try
                {
                    // create dispatcher and sync context
                    var d = Dispatcher.CurrentDispatcher;
                    var context = new DispatcherSynchronizationContext(d);
                    SynchronizationContext.SetSynchronizationContext(context);

                    // create taskfactory
                    eventLoopFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
                    eventLoopDispatcher = d;
                }
                finally
                {
                    lck.Set();
                }

                // run the event loop
                Dispatcher.Run();
            });
            t.SetApartmentState(ApartmentState.STA);
            t.IsBackground = true;
            t.Start();

            lck.WaitOne();
            lck.Dispose();

            eventLoopThread = t;
        }
    }
}

然后我像这样调用第二个线程:

async void button_click(...)
{
    // do something 1

    await eventLoopFactory.StartNew(()=>
    {
        // do something 2
    });

    // do something 3
}

有没有更好的方法?

【问题讨论】:

  • EnsureThread 方法对我来说看起来很可疑。你想达到什么目的?目前还不清楚。请更新问题。
  • 完成。简而言之,我正在使用 mciSendString,它需要一个带有事件循环的线程,但不希望它在 UI 线程上运行,因为它会因 UI 而变慢。

标签: c# .net wpf async-await


【解决方案1】:

如果你想在ThreadPool 的线程上运行委托,你实际上并不需要使用线程工厂

随便用

        await Task.Run(() =>
        {
            // do something 2
        });

这样,您无需在事件循环线程上运行 // do something 代码,而是在线程池中的可用线程上运行。

您不应该自己创建第二个线程。线程池是正确的工具,因为它会以一种非常有效的方式回收空闲线程。

【讨论】:

  • 您好,目标线程必须是线程池。每次都必须是同一个线程;因此TaskScheduler。我正在使用 MCI,它需要在带有事件循环的 STA 线程上运行。通常你会使用 UI 线程,但它会减慢 UI 并希望将其移开。
  • 你能解释一下为什么它真的总是需要在同一个线程上运行吗?
  • 我觉得我已经这样做了。请参阅 MCI (msdn.microsoft.com/en-us/library/dd757161(v=vs.85).aspx) 的文档。注意 hwnd 参数,暗示需要事件循环。此方法根本不适用于 MTA 或未运行 Dispatcher 的任意线程。
  • 好的,知道了!如果我理解正确,有两件事:任务是 MTA 设计的,因此,您不能使用它们。第二件事是:您希望避免每次必须运行代码时都从头开始创建新线程,对吗?对于第一部分,有人已经解决了它here,但不幸的是,它对第二部分没有帮助。我会试着考虑一些事情。
  • 感谢 Fabio,但我已经解决了您列举的两个问题。 EnsureThread 方法保持线程 STA 和单例。我的问题是如何击败使用微软推荐的异步模式。我对 EAP 和 APM 异步模式有更多经验。
【解决方案2】:

您的Button_Click 似乎已经做了太多事情,您应该先将此方法拆分为单独的方法。

async void Button_Click(...)
{
    await DoSomething1();
    await DoSomething2();
    await DoSomething3();
}

现在,您的其他方法将如下所示:

async Task DoSomething1()
{
    await Task.Run(() =>
    {
        ...
    });
}

这将允许您的 Button_Click 方法异步执行这些任务(按顺序)并保持您的 UI 响应。

【讨论】:

  • 我给出的示例经过简化,使其更具可读性。有问题的实际方法更像是:TakeLock(); RunTask(); ReleaseLock();
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-12
  • 1970-01-01
  • 1970-01-01
  • 2018-03-11
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多