C# 暂停线程直到服务器响应

C# Suspending Thread until Server responds

我正在尝试创建一个函数,当被调用时 returns 将信息返回给服务器上的调用者。我在这个函数中想要的是,它创建一个向服务器发出命令的线程,然后暂停自身,直到服务器用答案响应。

public AccountState GetAccount(string key)
{
  AccountState state = null;
  Thread t = new Thread(() =>
  {
    _connection.SomeCommandSentToServer(key);
    accountRequests.TryAdd(key, (Thread.CurrentThread, null));
    //Suspend current thread until ServerReponseHere is called
    Thread.CurrentThread.Suspend();
    //We have been resumed, value should be in accountRequests now
    accountRequests.TryRemove(key, out var item);
    state = item.AccountState;
  });
  t.Start();

  return state;
}


public ConcurrentDictionary<string, (Thread Thread, AccountState AccountState)> accountRequests = new ConcurrentDictionary<string, (Thread Thread, AccountState AccountState)>();

///Once server is done with processed command, call to this function made
public void ServerReponseHere(string key, AccountState state)
{
  accountRequests.TryGetValue(username, out var item);
  accountRequests.TryUpdate(username, (item.Thread, new AccountState()), item);
  item.Thread.Resume();
}

我的想法是,在不同的函数中,当服务器响应时,它调用上面显示的 ResumeThread 函数。

C# 表示 Suspend / Resume 是已弃用的函数,但是,执行此操作的更好方法是什么?


更新

关于 "SomeCommandSentToServer" 的说明 -- 这只是通过 TCP 套接字向服务器发送命令。

在那个调用中,真正发生的只是向服务器的传输。我正在使用一个使用 WinSock2.h 调用 "Send()" 的库——是的,我知道它是一个已弃用的库……但我正在使用的库需要它。

我有一个单独的线程来轮询来自服务器的输入。所以我无法在这个 SomeCommandSentToServer 上 "await"——我需要等待某种回调函数(也就是我提到的恢复函数)——才能完成这项工作。

我不确定该怎么做

根据问题提供的所有信息,以下是您在使用 async / await 模式时应该瞄准的目标:

public async Task<AccountState> GetAccountAsync(string key)
{
    // The method SomeCommandSentToServerAsync must be changed to support async.
    AccountState state = await _connection.SomeCommandSentToServerAsync(key);

    return state;
}

您不太可能需要其他任何东西。那样的话,我的意思是您 不必 必须直接操作线程,将它们放在并发字典中并手动挂起或恢复它们,因为从维护的角度来看这看起来很糟糕 ;)

.NET 将处理线程部分,这意味着 async 基础设施的魔力很可能会释放当前线程(假设实际上对服务器进行了调用)直到服务器 return是回应。

然后基础结构将使用现有的同步上下文 -如果你在一个 UI 线程上,例如 - 或者从线程池中获取一个线程 -如果不是- 运行 方法的其余部分。

您甚至可以通过简单地 returning a TaskAccountState:

类型的结果来进一步减少方法的大小
public Task<AccountState> GetAccountAsync(string key)
{
    // The method SomeCommandSentToServerAsync must be changed to support async.
    return _connection.SomeCommandSentToServerAsync(key);
}

在这两个示例中,您还必须让呼叫者 async:

public async Task TheCallerAsync()
{
    // Grab the key from somewhere.
    string key = ...;

    var accountState = await <inst>.GetAccountAsync(key);

    //  Do something with the state.
    ...
}

将遗留方法转变为异步方法

现在,关于遗留 SomeCommandSentToServer 方法。有一种方法可以等待该遗留方法。是的,您可以将该方法转换为可与 async / await 一起使用的异步方法。

当然,我没有您实施的所有细节,但我希望您能了解需要做什么。神奇的 class 叫做 TaskCompletionSource

它允许您做的是让您可以访问 Task。您创建 TaskCompletionSource class 的实例,将其保存在某处,发送命令并立即 return 该新实例的任务 属性。

从轮询线程获得结果后,您将获取 TaskCompletionSource 的实例,获取 AccountState 并使用帐户状态调用 SetResult。这会将任务标记为已完成并执行您要求的简历部分:)

想法是这样的:

    public Task<AccountState> SomeCommandSentToServerAsync(string key)
    {
        var taskCompletionSource = new TaskCompletionSource<AccountState>();

        //  Find a way to keep the task in some state somewhere
        //  so that you can get it the polling thread.

        //  Do the legacy WinSock Send() command.

        return taskCompletionSource.Task;
    }

    // This would be, I guess, your polling thread.
    // Again, I am sure it is not 100% accurate but 
    // it will hopefully give you an idea of where the key pieces must be.
    private void PollingThread()
    {
         while(must_still_poll)
         {
             //  Waits for some data to be available.

             //  Grabs the data.

             if(this_is_THE_response)
             {
                 // Get the response and built the account state somehow...

                 AccountState accountState = ...

                 // Key piece #1
                 // Grab the TaskCompletionSource instance somewhere.

                 // Key piece #2
                 // This is the magic line:
                 taskCompletionSource.SetResult(accountState);

                 // You can also do the following if something goes wrong:
                 // taskCompletionSource.SetException(new Exception());
             }
         }
    }