Estou tendo muita dificuldade em descobrir se é possível realizar um clique virtual em um elemento de formulário C# de outro elemento e aguardar a conclusão antes de continuar.
Por exemplo (infelizmente incompleto), o que estou tentando fazer é o seguinte
private async void btn_my_Click(object sender, EventArgs e)
{
await Task.Run(() => radio_button_special.PerformClick()); // https://stackoverflow.com/q/14024963
// once this is done, proceed with rest of processing
do_rest_of_processing();
}
Quando clico no botão na execução de depuração, o VS2019 quebra com "InvalidOperationException: operação entre threads inválida: controle 'radio_button_special' acessado de um thread diferente do thread em que foi criado."
No entanto, se eu apenas executar o exe fora da depuração do Visual Studio, o botão parece funcionar (ou seja, posso ver no aplicativo que radio_button_special.PerformClick() é concluído primeiro e do_rest_of_processing() é executado depois - e não recebo uma exceção explícita funcionando assim, então parece funcionar como eu imaginei).
Mas essa exceção me assusta, então gostaria de me livrar dela - e tentei remediar assim, que tentei construir a partir de outros trechos no SO:
private async void btn_my_Click(object sender, EventArgs e)
{
// first, I want to call the function otherwise called
// when the radio button is clicked, and wait for it to complete
radio_button_special.Invoke((MethodInvoker)async delegate
{
await Task.Run(() => radio_button_special.PerformClick()); // https://stackoverflow.com/q/14024963
});
// once this is done, proceed with rest of processing
do_rest_of_processing();
}
Agora, essa função já dá um aviso na IDE do VS2019:
aviso CS1998: Este método assíncrono não possui operadores 'await' e será executado de forma síncrona. Considere usar o operador 'await' para aguardar chamadas de API sem bloqueio ou 'await Task.Run(...)' para realizar trabalho vinculado à CPU em um thread em segundo plano.
... e se eu depurar, execute o exemplo, recebo o rastreamento de pilha:
System.Reflection.TargetInvocationException
HResult=0x80131604
Message=Exception has been thrown by the target of an invocation.
Source=mscorlib
StackTrace:
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
...
at System.Windows.Forms.Application.Run(Form mainForm)
at my_test.Program.Main() in D:\work\bbs\BBS_DEV\BAPS_git\my_test\Program.cs:line 28
This exception was originally thrown at this call stack:
System.Windows.Forms.Control.Handle.get()
System.Windows.Forms.Control.InternalHandle.get()
System.Windows.Forms.Control.Update()
System.Windows.Forms.ButtonBase.ResetFlagsandPaint()
System.Windows.Forms.RadioButton.PerformClick()
my_test.main_form.btn_my_Click.AnonymousMethod__122_1() in main_form.cs
System.Threading.Tasks.Task.InnerInvoke()
System.Threading.Tasks.Task.Execute()
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(System.Threading.Tasks.Task)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(System.Threading.Tasks.Task)
...
[Call Stack Truncated]
Inner Exception 1:
InvalidOperationException: Cross-thread operation not valid: Control 'radio_button_special' accessed from a thread other than the thread it was created on.
Então, basicamente o mesmo problema de antes.
Então, como eu poderia chamar uma função de manipulador de componente de formulário C # (por exemplo, clique) de outro manipulador de componente de formulário, aguardar a conclusão e, em seguida, chamar outra função - sem gerar uma exceção?
Se você quiser apenas chamar um método de outro, isso funcionaria:
Task.Run
filas funcionam no pool de threads, o que está incorreto aqui.Se você tiver um trabalho longo a fazer, será melhor para seus usuários se você executar esse trabalho em um thread de pool de threads. A solução adequada nesse caso é refatorar seu código para que haja outro método que possa ser chamado tanto pelo clique do botão de opção quanto pelo clique deste botão.
Task.Run
enfileira o método fornecido para execução em um thread do pool de threads. Portanto, você ainda está acessando o controle de outro thread, mesmo executando uma invocação. Em vez disso, você deve chamar oControl.Invoke
interior do arquivoTask.Run
.EDIT: o que funcionou para o pôster do OP foi: