gpt4 book ai didi

c# - 如何从必须留在主线程上的方法统一调用异步方法

转载 作者:行者123 更新时间:2023-12-04 15:56:18 26 4
gpt4 key购买 nike

我有一个名为 SendWithReplyAsync 的方法,它使用 TaskCompletionSource 在完成时发出信号并从服务器返回回复。

我正在尝试通过一种方法从服务器获得 2 个回复,该方法还需要更改场景、更改转换等,因此它需要在主线程上,据我所知。

unity中该方法绑定(bind)到一个ui按钮的OnClick():

public async void RequestLogin()
{
var username = usernameField.text;
var password = passwordField.text;
var message = new LoginRequest() { Username = username, Password = password };

var reply = await client.SendWithReplyAsync<LoginResponse>(message);

if (reply.Result)
{
Debug.Log($"Login success!");
await worldManager.RequestSpawn();
}
else Debug.Log($"Login failed! {reply.Error}");
}

如您所见,调用了 await WorldManager.RequestSpawn();

该方法如下所示:

public async Task RequestSpawn()
{
//Get the initial spawn zone and transform
var spawnReply = await client.SendWithReplyAsync<PlayerSpawnResponse>(new PlayerSpawnRequest());

//Load the correct zone
SceneManager.LoadScene("TestingGround");

//Move the player to the correct location
var state = spawnReply.initialState;
player.transform.position = state.Position.Value.ToVector3();
player.transform.rotation = Quaternion.Euler(0, state.Rotation.Value, 0);

//The last step is to get the visible entities at our position and create them before closing the loading screen
var statesReply = await client.SendWithReplyAsync<InitialEntityStatesReply>(new InitialEntityStatesRequest());

SpawnNewEntities(statesReply);
}

所以当我单击按钮时,我可以看到(服务器端)所有消息(登录请求、生成请求和初始实体状态请求)都在发送。然而,统一中什么也没有发生。没有场景变化,(显然)没有位置或旋转更新。

我有一种感觉,当涉及到统一时,我对 async/await 不了解,而且我的 RequestSpawn 方法没有在主线程上运行。

我尝试使用 client.SendWithReplyAsync(...).Result 并删除所有方法上的 async 关键字,但这只会导致死锁。我在 Stephen Cleary 的博客上阅读了更多关于死锁的信息 here (他的网站似乎占用了 100% 的 cpu.. 只有我一个吗?)

我真的不确定如何让它工作。

如果您需要它们,这里是发送/接收消息的方法:

public async Task<TReply> SendWithReplyAsync<TReply>(Message message) where TReply : Message
{
var task = msgService.RegisterReplyHandler(message);
Send(message);

return (TReply)await task;
}

public Task<Message> RegisterReplyHandler(Message message, int timeout = MAX_REPLY_WAIT_MS)
{
var replyToken = Guid.NewGuid();

var completionSource = new TaskCompletionSource<Message>();
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(timeout);
//TODO Make sure there is no leakage with the call to Token.Register()
tokenSource.Token.Register(() =>
{
completionSource.TrySetCanceled();
if (replyTasks.ContainsKey(replyToken))
replyTasks.Remove(replyToken);
},
false);

replyTasks.Add(replyToken, completionSource);

message.ReplyToken = replyToken;
return completionSource.Task;
}

这是完成任务的地点/方式:

private void HandleMessage<TMessage>(TMessage message, object sender = null) where TMessage : Message
{
//Check if the message is in reply to a previously sent one.
//If it is, we can complete the reply task with the result
if (message.ReplyToken.HasValue &&
replyTasks.TryGetValue(message.ReplyToken.Value, out TaskCompletionSource<Message> tcs) &&
!tcs.Task.IsCanceled)
{
tcs.SetResult(message);
return;
}

//The message is not a reply, so we can invoke the associated handlers as usual
var messageType = message.GetType();
if (messageHandlers.TryGetValue(messageType, out List<Delegate> handlers))
{
foreach (var handler in handlers)
{
//If we have don't have a specific message type, we have to invoke the handler dynamically
//If we do have a specific type, we can invoke the handler much faster with .Invoke()
if (typeof(TMessage) == typeof(Message))
handler.DynamicInvoke(sender, message);
else
((Action<object, TMessage>)handler).Invoke(sender, message);
}
}
else
{
Debug.LogError(string.Format("No handler found for message of type {0}", messageType.FullName));
throw new NoHandlersException();
}
}

万幸,传奇的 Stephen Cleary 看到了这个

最佳答案

假设您通过最新版本的 Unity 使用异步/等待,并将“.NET 4.x 等效”设置为脚本运行时版本,那么您编写的 RequestSpawn() 方法应该是在 Unity 的主线程上运行。您可以通过调用验证:

Debug.Log(System.Threading.Thread.CurrentThread.ManagedThreadId);

以下简单测试使用 Unity 2018.2(下面的输出)为我正确加载新场景:

public async void HandleAsync()
{
Debug.Log($"Foreground: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
await WorkerAsync();
Debug.Log($"Foreground: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
}

private async Task WorkerAsync()
{
await Task.Delay(500);
Debug.Log($"Worker: {Thread.CurrentThread.ManagedThreadId}");
await Task.Run((System.Action)BackgroundWork);
await Task.Delay(500);
SceneManager.LoadScene("Scene2");
}

private void BackgroundWork()
{
Debug.Log($"Background: {Thread.CurrentThread.ManagedThreadId}");
}

输出:

Foreground: 1

Worker: 1

Background: 48

Foreground: 1

关于c# - 如何从必须留在主线程上的方法统一调用异步方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51472393/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com