【问题标题】:Waiting for an object to be updated by another task C#等待另一个任务 C# 更新对象
【发布时间】:2020-06-10 02:24:08
【问题描述】:

我已经阅读了很多关于“等待”任务的帖子......我似乎无法弄清楚这一点,因为这可能是实际等待任务的一个转折点,我正在等待一个对象要更新,如果没有超时。

我正在使用 redis,它在单独的消息处理程序中从 redis 服务器接收到基本 acks/nack 列表时正确更新它们。 (发布的代码太多,与这个问题无关)。

有问题的代码是如何调用下面的“WaitForAck”方法,它的职责是检查上面的列表是否附加了它正在等待的 ack/nack。

因此,如果列表 'dic_DEAL_OUTPUTRESP_MessageQueue' 被单独的 redis 处理程序附加,我希望能够在任何它将等待(7 秒)的地方调用 'WaitForAck',基本上阻止用户前进但不阻止任何其他线程,例如等待其更新的 redis 消息处理程序线程。

如果这应该是任何一种等待任务,我想我有点困惑,因为我不希望它是异步的?我希望用户等待 7 秒以查看它是否收到 ack/nack(列表有值),否则它会继续通知用户超时(单独处理)。

这是我的“WaitForAck”方法:

    public static void WaitForAck(string origUuid)
    {
        DateTime dt = DateTime.Now;

        Cursor.Current = Cursors.WaitCursor;
        try
        {
            while (dic_DEAL_OUTPUTRESP_MessageQueue[origUuid] == null)
            {
                Debug.WriteLine("Waiting for Redis Ack");
                Task.Delay(1000);
                TimeSpan ts = DateTime.Now - dt;
                if (ts.TotalSeconds > iWAIT)
                {
                    break;
                }
            }
        }
        catch { }
        Cursor.Current = Cursors.Default;
    }

提前谢谢你

编辑:添加调用

 private async void btnEnter_Click(object sender, EventArgs e)
    {
        // Process trade entry / amendment
        if (ConfirmEntries() != 0)
        {
            this.DialogResult = DialogResult.None;
            return;
        }
        DefineDeal();

        // Attempt Commit
        DEAL_REQ dEAL_REQ_ADD = new DEAL_REQ();
        dEAL_REQ_ADD.msgTimestamp = ReturnUTCTime(DateTime.Now).ToString(sDATETIME_FORMATMS);
        dEAL_REQ_ADD.uuid = Guid.NewGuid().ToString();
        dEAL_REQ_ADD.commandKey = sMeFunction == sADD ? sDEAL_REQ_ADD : sDEAL_REQ_UPDATE;
        dEAL_REQ_ADD.listenChannel = sRPC_CHANNEL_OUTPUT;
        dEAL_REQ_ADD.payload = _fxTempDeal;
        // Build json msg
        string sJson = JsonConvert.SerializeObject(dEAL_REQ_ADD);
        // Publish request
        // Prime response queue with guid in key
        dic_DEAL_OUTPUTRESP_MessageQueue.Add(dEAL_REQ_ADD.uuid, null);
        //Task.Delay(100);
        IDatabase db = redisConn.GetDatabase(redisDB);

        db.ListLeftPush(sRPC_CHANNEL_INPUT, sJson);
        //Task.Delay(100);
        //string s = db.ListLeftPop(sRPC_LB_INPUT);

        // Wait for ack 
        var value = await this.WaitForAck2(dEAL_REQ_ADD.uuid);

        // Received ack/nack
        if (value != null)
        {
            DEAL_RESP rESP = dic_DEAL_OUTPUTRESP_MessageQueue[dEAL_REQ_ADD.uuid];

            if (rESP.commandKey == sDEAL_REQ_ADD_ACK || rESP.commandKey == sDEAL_REQ_UPDATE_ACK)
            {
                this.DialogResult = DialogResult.OK;
            }
            else if (rESP.commandKey == sDEAL_REQ_ADD_NACK || rESP.commandKey == sDEAL_REQ_UPDATE_NACK)
            {
                MessageBox.Show("Unable to " + sMeFunction + " this deal. Please contact support. Reason : " + rESP.payload.display_msg, sAPP_NAME, MessageBoxButtons.OK, MessageBoxIcon.Error);
                this.DialogResult = DialogResult.Abort;
            }
        }
        // Timed out
        else
        {
            MessageBox.Show("Unable to " + sMeFunction + " this deal. Please contact support. Reason : Request Timeout", sAPP_NAME, MessageBoxButtons.OK, MessageBoxIcon.Error);
            dic_DEAL_OUTPUTRESP_MessageQueue.Remove(dEAL_REQ_ADD.uuid);
            dic_DEAL_TimedOut_MessageQueue.Add(dEAL_REQ_ADD.uuid, null);
            this.DialogResult = DialogResult.Cancel;
        }
    }

    public async Task<DEAL_RESP> WaitForAck2(string origUuid)
    {
        Stopwatch timer = new Stopwatch();
        timer.Start();

        while (timer.ElapsedMilliseconds < (7 * 1000))
        {
            if (await this.sync.WaitAsync(TimeSpan.FromSeconds(7)))
            {
                try
                {
                    if (dic_DEAL_OUTPUTRESP_MessageQueue.TryGetValue(origUuid, out var value))
                    {
                        return value;
                    }
                }
                finally
                {
                    this.sync.Release();
                }
            }

            await Task.Delay(1000);
        }

        return null;
    }

【问题讨论】:

  • 不能给更新的方法引入回调?
  • 我会查看 .net 中的线程原语 - 例如 ManualResetEvent
  • 据我了解:您的实际问题是您有一个可锁定的资源。您想针对该资源启动一个进程(必须遵守锁),即使它可能已被锁定。如果它被锁定,该进程应该能够等待(正常的锁定行为),但如果获取锁定它需要很长时间(不是正常的锁定行为)也会超时?
  • 异常处理是我最讨厌的事情,这太可怕了。你吞下了所有的例外。这是在一条可怕的线路中处理异常的两个主要罪过。正如它所写的那样,那里甚至不应该有 be 一个 try 块。我收到了两篇我经常链接的关于异常处理的文章。他们应该帮助避免你做这样的代码:blogs.msdn.microsoft.com/ericlippert/2008/09/10/… | codeproject.com/Articles/9538/…
  • @itsme86 我不相信我可以做回调?因为它只是一个完全解耦的发布/订阅设置,即:一个进程请求更新到服务器,服务器发布一个 ack/nack 这个方法等待它......但它可能永远不会到来

标签: c# .net asynchronous


【解决方案1】:

我认为您对您的问题有所回击,因为线程要求的上下文尚不清楚。如果这只是在服务中运行的两个线程,那么可以使用简单的 autoresetevent 来表示新消息已添加到队列中,然后该线程可以响应。但是从你的例子看来——由于 Cursor 类的使用——你在 UI 线程中运行,这使它变得复杂。您不能阻止 UI 线程。

我怀疑有比轮询队列 7 秒更好的 UI 模式来处理这个问题。但如果这符合您当前的设计,那么我认为以下代码将启用异步轮询。我使用一个简单的控制台应用程序对此进行了测试,并且线程按预期工作。外部类没有显示..

class MyClass 
{
    SemaphoreSlim sync = new SemaphoreSlim(1, 1);
    Dictionary<string, string> messages = new Dictionary<string, string>();

    public async void OnButtonClicked(EventArgs sender)
    {
        string origUuid = string.Empty; // get key from button click?

        var value = await this.WaitForAck(origUuid);

        if (null != value)
        {
            /// display it somehow...
            Console.WriteLine(value);
        }
    }

    public async Task<string> WaitForAck(string origUuid)
    {
        Stopwatch timer = new Stopwatch();
        timer.Start();

        while (timer.ElapsedMilliseconds < (7 * 1000))
        {
            if (await this.sync.WaitAsync(TimeSpan.FromSeconds(7)))
            {
                try
                {
                    if (this.messages.TryGetValue(origUuid, out var value))
                    {
                        return value;
                    }
                }
                finally
                {
                    this.sync.Release();
                }
            }

            await Task.Delay(1000);
        }

        return null;
    }

    public async Task PushAck(string origUuid, string value)
    {
        await this.sync.WaitAsync();

        try
        {
            this.messages.Add(origUuid, value);
        }
        finally
        {
            this.sync.Release();
        }
    }
}

class Program
{
    static async Task Main(string[] args)
    {
        var myform = new MyClass();

        var pub = Task.Run(() =>
        {
            int index = 0;

            while (true)
            {
                Task.Delay(1000).Wait();

                myform.PushAck(index.ToString(), "gotit").Wait();

                ++index;
            }
        });

        Stopwatch timer = new Stopwatch();
        timer.Start();

        var mymessage = await myform.WaitForAck("1");
        timer.Stop();

        Console.WriteLine($"message \"{mymessage}\" recieved in {timer.ElapsedMilliseconds} ms");
    }

【讨论】:

  • 谢谢 Mike,我会看看这个
  • @MX313 我已经从您的轮询方法中删除了 UI 位,您可以从异步 void 按钮处理程序方法中调用它并调整您的光标或其中的任何其他 UI 更改。
  • 我在哪里可以备份此代码?即:目前我调用 WaitForAck 并在那里等待,即:它不会返回,直到出现应答 ack/nack 或超时并继续下一行 // Wait for ack 如下:WaitForAck(dEAL_REQ_ADD.uuid); // 如果 (dic_DEAL_OUTPUTRESP_MessageQueue[dEAL_REQ_ADD.uuid] != null) 接收到 ack/nack。由于这个新代码直接通过并且是异步的,当 if (this.messages.TryGetValue(origUuid, out var value)) 为真时,我应该在哪里获取它的响应?感谢您的帮助
  • 我应该只使用一种等待方法,例如 Task output = Task.Factory.StartNew(WaitForAck);输出.Wait(); ?
  • @MX313 不,不要用等待阻塞 UI 线程。我添加了一个按钮 onclick 处理程序。您可能必须调用回 ui 线程来显示结果。您必须进行测试,否则其他人可能会做出回应。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-02
  • 1970-01-01
  • 1970-01-01
  • 2019-02-07
  • 2018-04-15
相关资源
最近更新 更多