IpcMain : add support for async callbacks #474

Closed
opened 2026-01-29 16:40:40 +00:00 by claunia · 10 comments
Owner

Originally created by @duncanawoods on GitHub (Mar 31, 2020).

Originally assigned to: @GregorBiswanger on GitHub.

Callbacks triggered by the UI are async (with respect to the web UI) but are handled with a non-async C# action:

Electron.IpcMain.On(string channel, Action<object> listener)

It is now common for C# controller methods to be async so it would be convenient to support them directly with the following overload i.e.

Electron.IpcMain.On(string channel, Func<object, Task> listener)

Bridging the sync -> async boundary, might just mean using Task.Run but I don't know whether running it on the threadpool is actually optimal because I assume On is already being invoked on it's own thread. By implementing this API, hopefully you are able to make the right choice about how to execute the async method without adding unnecessary overhead.

//
// Is invoking on the threadpool ideal?
//
public void On(string channel, Func<object, Task> listener) =>
    this.On(
        channel, 
        args => Task.Run(async () => await listener(args)));
Originally created by @duncanawoods on GitHub (Mar 31, 2020). Originally assigned to: @GregorBiswanger on GitHub. Callbacks triggered by the UI are async (with respect to the web UI) but are handled with a non-async C# action: ``` csharp Electron.IpcMain.On(string channel, Action<object> listener) ``` It is now common for C# controller methods to be async so it would be convenient to support them directly with the following overload i.e. ``` csharp Electron.IpcMain.On(string channel, Func<object, Task> listener) ``` Bridging the sync -> async boundary, might just mean using `Task.Run` but I don't know whether running it on the threadpool is actually optimal because I assume `On` is already being invoked on it's own thread. By implementing this API, hopefully you are able to make the right choice about how to execute the async method without adding unnecessary overhead. ``` csharp // // Is invoking on the threadpool ideal? // public void On(string channel, Func<object, Task> listener) => this.On( channel, args => Task.Run(async () => await listener(args))); ```
claunia added the help wantedFeature labels 2026-01-29 16:40:40 +00:00
Author
Owner

@GregorBiswanger commented on GitHub (Apr 17, 2020):

The callback is Async. Use with Async-Await is also possible if you execute asynchronous code in the callback.

Or did I misunderstand you?

@GregorBiswanger commented on GitHub (Apr 17, 2020): The callback is Async. Use with Async-Await is also possible if you execute asynchronous code in the callback. Or did I misunderstand you?
Author
Owner

@yawnston commented on GitHub (Apr 18, 2020):

While it is technically possible to use async-await in the callback, if the callback produces an exception then this exception will be completely ignored since the Task would not be awaited. What @duncanawoods is proposing unfortunately doesn't seem like the solution either since Task.Run itself returns a Task which would not be awaited, so it seems like it's just shifting the problem 1 level deeper.

I actually did a little bit of digging and it looks like supporting this wouldn't be such an easy fix, since the Quobject.SocketIoClientDotNet library used by Electron.NET doesn't seem to support awaiting async callbacks on its Socket class. Sidenote is that the mentioned Socket.IO client library is deprecated and not maintained anymore, so maybe if this project decides to switch its Socket.IO client library, this issue could be solved at the same time. This issue (https://github.com/Quobject/EngineIoClientDotNet/issues/69) discusses some client library alternatives.

Right now I think the only workaround would be to just block on the Func<object, Task> listener. That might be okay considering this library is only meant to be used from ASP.NET Core anyway, but it's still probably a better idea to leave it as it is for now. Blocking on async code gives me the jitters.

@yawnston commented on GitHub (Apr 18, 2020): While it is technically possible to use async-await in the callback, if the callback produces an exception then this exception will be completely ignored since the `Task` would not be awaited. What @duncanawoods is proposing unfortunately doesn't seem like the solution either since `Task.Run` itself returns a `Task` which would not be awaited, so it seems like it's just shifting the problem 1 level deeper. I actually did a little bit of digging and it looks like supporting this wouldn't be such an easy fix, since the `Quobject.SocketIoClientDotNet` library used by Electron.NET doesn't seem to support awaiting async callbacks on its `Socket` class. Sidenote is that the mentioned Socket.IO client library is deprecated and not maintained anymore, so maybe if this project decides to switch its Socket.IO client library, this issue could be solved at the same time. This issue (https://github.com/Quobject/EngineIoClientDotNet/issues/69) discusses some client library alternatives. Right now I think the only workaround would be to just block on the `Func<object, Task> listener`. That _might_ be okay considering this library is only meant to be used from ASP.NET Core anyway, but it's still probably a better idea to leave it as it is for now. Blocking on async code gives me the jitters.
Author
Owner

@GregorBiswanger commented on GitHub (Apr 20, 2020):

I am currently working on version 8.31.1 - Trying a completely different Socket.IO .NET client will be a bit short of time ... I will be the father of my second son in early May and will be somewhat limited .. if necessary someone has try another Socket.IO client from the community time ..

@GregorBiswanger commented on GitHub (Apr 20, 2020): I am currently working on version 8.31.1 - Trying a completely different Socket.IO .NET client will be a bit short of time ... I will be the father of my second son in early May and will be somewhat limited .. if necessary someone has try another Socket.IO client from the community time ..
Author
Owner

@duncanawoods commented on GitHub (Apr 20, 2020):

@yawnston

if the callback produces an exception

In most GUI APIs, an unhandled exception in a callback is application fatal. It would be normal for a callback to encapsulate it's errors rather than bubble them up to a framework that can't do anything sensible with them.

doesn't seem like the solution either since Task.Run itself returns a Task which would not be awaited

Task.Run returns a task but it doesn't need to be awaited unless you wish to capture the result or block until the asynchronous execution on the thread-pool is complete. Neither of these are relevant for supplying a callback.

A real reason this and other such workarounds that try to force async code to be executed by a sync framework, should be avoided, is deadlock.

@duncanawoods commented on GitHub (Apr 20, 2020): @yawnston > if the callback produces an exception In most GUI APIs, an unhandled exception in a callback is application fatal. It would be normal for a callback to encapsulate it's errors rather than bubble them up to a framework that can't do anything sensible with them. > doesn't seem like the solution either since Task.Run itself returns a Task which would not be awaited `Task.Run` returns a task but it doesn't need to be awaited unless you wish to capture the result or block until the asynchronous execution on the thread-pool is complete. Neither of these are relevant for supplying a callback. A real reason this and other such workarounds that try to force async code to be executed by a sync framework, should be avoided, is deadlock.
Author
Owner

@dafergu2 commented on GitHub (May 18, 2020):

I snooped around a little on this. Perhaps looking at this as a replacement would be good?

https://github.com/HavenDV/H.Socket.IO/

At the very least, the replies back to the electron main process can use async/await. I'd want to dive into it a bit to see if the callbacks can accept an async method as well.

@dafergu2 commented on GitHub (May 18, 2020): I snooped around a little on this. Perhaps looking at this as a replacement would be good? https://github.com/HavenDV/H.Socket.IO/ At the very least, the replies back to the electron main process can use async/await. I'd want to dive into it a bit to see if the callbacks can accept an async method as well.
Author
Owner

@marcianobandeira commented on GitHub (Feb 15, 2021):

currently, electron have handle and invoke methods, wich provides async await calls

// Renderer process
ipcRenderer.invoke('some-name', someArgument).then((result) => {
// ...
})

// Main process
ipcMain.handle('some-name', async (event, someArgument) => {
const result = await doSomeWork(someArgument)
return result
})

it's possible in Electron.NET?

@marcianobandeira commented on GitHub (Feb 15, 2021): currently, electron have handle and invoke methods, wich provides async await calls // Renderer process ipcRenderer.invoke('some-name', someArgument).then((result) => { // ... }) // Main process ipcMain.handle('some-name', async (event, someArgument) => { const result = await doSomeWork(someArgument) return result }) it's possible in Electron.NET?
Author
Owner

@nazar322 commented on GitHub (May 14, 2021):

currently, electron have handle and invoke methods, wich provides async await calls

// Renderer process
ipcRenderer.invoke('some-name', someArgument).then((result) => {
// ...
})

// Main process
ipcMain.handle('some-name', async (event, someArgument) => {
const result = await doSomeWork(someArgument)
return result
})

it's possible in Electron.NET?

@GregorBiswanger Indeed, will this be available in Electron.NET?
How can I reply to the renderer that called ipcRenderer.invoke?
This API is very needed as remote is deprecated.
Specifically, I have a custom title bar in the app, and from the renderer, I would like to know if the window is maximized.

@nazar322 commented on GitHub (May 14, 2021): > currently, electron have handle and invoke methods, wich provides async await calls > > // Renderer process > ipcRenderer.invoke('some-name', someArgument).then((result) => { > // ... > }) > > // Main process > ipcMain.handle('some-name', async (event, someArgument) => { > const result = await doSomeWork(someArgument) > return result > }) > > it's possible in Electron.NET? @GregorBiswanger Indeed, will this be available in Electron.NET? How can I reply to the renderer that called `ipcRenderer.invoke`? This API is very needed as `remote` is deprecated. Specifically, I have a custom title bar in the app, and from the renderer, I would like to know if the window is maximized.
Author
Owner

@GregorBiswanger commented on GitHub (Mar 28, 2023):

🎉🚀 New Electron.NET version 23.6.1 released 🚀🎉

With native Electron 23 and .NET 6 support. Your problem should be fixed here. If you continue to have the problem, please let us know. Please note the correct updating of your API & CLI. Info in the README. Have fun!

@GregorBiswanger commented on GitHub (Mar 28, 2023): 🎉🚀 New Electron.NET version 23.6.1 released 🚀🎉 With native Electron 23 and .NET 6 support. Your problem should be fixed here. If you continue to have the problem, please let us know. Please note the correct updating of your API & CLI. Info in the README. Have fun!
Author
Owner

@Yuvix25 commented on GitHub (Apr 28, 2023):

@GregorBiswanger
With the new version, is there an equivalent of Electron's ipcMain.handle?

@Yuvix25 commented on GitHub (Apr 28, 2023): @GregorBiswanger With the new version, is there an equivalent of Electron's `ipcMain.handle`?
Author
Owner

@lukedukeus commented on GitHub (Feb 20, 2024):

@GregorBiswanger If I want to get a result back from ipc.invoke("someMethod") on the client?

on the client side, I have:

const result =  await ipc.invoke('OpenFileDialog');

on the server side:

          Electron.IpcMain.On("OpenPINFiles", async (args) =>
          {
              List<string> files = new List<string>() { "somefile" };

            
          });

How can I modify this to get the result back on the client side?

@lukedukeus commented on GitHub (Feb 20, 2024): @GregorBiswanger If I want to get a result back from `ipc.invoke("someMethod")` on the client? on the client side, I have: ``` const result = await ipc.invoke('OpenFileDialog'); ``` on the server side: ``` Electron.IpcMain.On("OpenPINFiles", async (args) => { List<string> files = new List<string>() { "somefile" }; }); ``` How can I modify this to get the result back on the client side?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/Electron.NET#474