RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1385259
Accepted
aepot
aepot
Asked:2022-07-24 00:45:45 +0000 UTC2022-07-24 00:45:45 +0000 UTC 2022-07-24 00:45:45 +0000 UTC

如何在一个线程中执行异步代码

  • 772

之前,我已经写了一个类似问题的答案:How to ensure code execution in 1 thread after await c#.net

但是需要在应用程序中重用单线程同步的魅力,而无需使用异步代码进行任何阻塞。众所周知,lock其他同步原语不能在异步代码中使用。好吧,除了信号量,但信号量并不是很原始。

要重现问题,您甚至不需要集合,您可以运行以下代码:

static async Task Main(string[] args)
{
    await AddAsync(1000);
    Console.WriteLine(_counter);
}

static int _counter;

static async Task AddAsync(int count)
{
    Task[] tasks = new Task[count];
    for (int i = 0; i < count; i++)
        tasks[i] = IncrementAsync();
    await Task.WhenAll(tasks);
}

static async Task IncrementAsync()
{
    await Task.Delay(1);
    _counter++;
}

并在控制台中获取例如此输出

969

它应该是1000。这意味着代码不是线程安全的。如果您从 WPF 或 Winforms 中的 UI 线程运行此代码,它将 100% 正常工作,并且始终返回1000. 在通过引用提出的解决方案中,上下文本身的启动看起来非常麻烦,并且对于每次启动,您都需要创建一个新线程或将当前线程提供给上下文。

我想找到一个更简单的解决方案,以便您可以将多线程代码的不同部分中的调用重定向到您自己在特定线程中运行的某种上下文。

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    aepot
    2022-07-24T00:45:45Z2022-07-24T00:45:45Z

    解决方案与过去非常相似,略有不同。但它比上一个更容易使用。您可以拥有一个上下文并从应用程序的不同角落重用它。

    该解决方案的本质是创建一个上下文,该上下文从池中获取 1 个线程并在其中工作,直到它被处理掉。同时,参考上下文实例,您可以将调用从代码中的任何位置重定向到同一个线程。

    你可以将任何代码扔到上下文中,包括同步的,但是这个类的目的是让运行异步方法成为可能,这样方法await内部的线程在异步调用完成后不会改变线程,就像它一样发生在桌面应用程序的 UI 线程中。对此负责的方法有 2 个重载SendAsync。

    也就是说,可以使用单个线程的普通集合和其他乐趣而​​不会在代码中阻塞。这个解决方案,即使对于 UI 应用程序,当您想要像在 UI 线程中一样工作async/await但同时不加载 UI 本身时也会很有用。

    public class SingleThreadedSynchronizationContext : SynchronizationContext, IDisposable
    {
        private readonly BlockingCollection<(SendOrPostCallback, object)> _queue = new();
        private readonly Task _task;
    
        public SingleThreadedSynchronizationContext()
        {
            _task = Task.Run(RunContext);
        }
    
        public override void Post(SendOrPostCallback d, object state)
        {
            _queue.Add((d, state));
        }
    
        public override void Send(SendOrPostCallback d, object state)
        {
            if (Current == this)
                d(state);
            else
            {
                using ManualResetEventSlim mre = new();
                Post(s =>
                {
                    try
                    {
                        d(s);
                    }
                    finally
                    {
                        mre.Set();
                    }
                }, state);
                mre.Wait();
            }
        }
    
        public Task SendAsync(Func<Task> func)
        {
            if (Current == this)
                return func();
            Task task = null;
            Send(_ => task = func(), null);
            return task;
        }
    
        public Task<T> SendAsync<T>(Func<Task<T>> func)
        {
            if (Current == this)
                return func();
            Task<T> task = null;
            Send(_ => task = func(), null);
            return task;
        }
    
        private void RunContext()
        {
            SetSynchronizationContext(this);
            foreach ((SendOrPostCallback d, object state) in _queue.GetConsumingEnumerable())
            {
                d(state);
            }
        }
    
        public void Dispose()
        {
            _queue.CompleteAdding();
            _task.Wait();
        }
    }
    

    我正在查。

    static async Task Main(string[] args)
    {
        using SingleThreadedSynchronizationContext context = new();
        await context.SendAsync(() => AddAsync(1000));
        Console.WriteLine(_counter);
    }
    

    控制台输出

    1000
    

    请注意,此上下文不处理异常。一般来说,同步上下文本身并不打算用于异常处理,它实际上是在处理它们await。因此,我没有加载带有处理的代码。但是你应该小心。如果Post您手动发送会引发异常的委托,则上下文将下降并且将不再能够处理调用。Send如果你不手动Post调用它,那么你不需要修改任何东西。


    我展示了如何完全保护上下文不引发异常。为此,修改几个方法非常简单。

    public override void Send(SendOrPostCallback d, object state)
    {
        if (Current == this)
            d(state);
        else
        {
            using ManualResetEventSlim mre = new();
            ExceptionDispatchInfo edi = null;
            Post(s =>
            {
                try
                {
                    d(s);
                }
                catch (Exception ex)
                {
                    edi = ExceptionDispatchInfo.Capture(ex);
                }
                mre.Set();
            }, state);
            mre.Wait();
            edi?.Throw();
        }
    }
    
    private void RunContext()
    {
        SetSynchronizationContext(this);
        foreach ((SendOrPostCallback d, object state) in _queue.GetConsumingEnumerable())
        {
            try
            {
                d(state);
            }
            catch { }
        }
    }
    

    我再说一遍,只有当你从上下文之外抛出异常时,Send才需要这样的细化。Post如果您只使用SendAsync,则不需要此细化。

    • 2

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    我看不懂措辞

    • 1 个回答
  • Marko Smith

    请求的模块“del”不提供名为“default”的导出

    • 3 个回答
  • Marko Smith

    "!+tab" 在 HTML 的 vs 代码中不起作用

    • 5 个回答
  • Marko Smith

    我正在尝试解决“猜词”的问题。Python

    • 2 个回答
  • Marko Smith

    可以使用哪些命令将当前指针移动到指定的提交而不更改工作目录中的文件?

    • 1 个回答
  • Marko Smith

    Python解析野莓

    • 1 个回答
  • Marko Smith

    问题:“警告:检查最新版本的 pip 时出错。”

    • 2 个回答
  • Marko Smith

    帮助编写一个用值填充变量的循环。解决这个问题

    • 2 个回答
  • Marko Smith

    尽管依赖数组为空,但在渲染上调用了 2 次 useEffect

    • 2 个回答
  • Marko Smith

    数据不通过 Telegram.WebApp.sendData 发送

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5