如何在C#桌面应用中调用Web API且无需等待响应
Hey there! Let's break down your problem and fix it step by step. The timeout error you're seeing happens because your desktop app is blocking to wait for the API to send a full response, while your API methods use async void (which causes issues in ASP.NET controllers) and run long operations. Here's how to adjust both your Web API and desktop app to get the "fire-and-forget" behavior you want:
1. Fix Your Web API Controller Methods
First off, async void is not recommended for ASP.NET controller actions—it prevents the runtime from tracking when the action completes, which can lead to hanging requests and timeouts. Switch to async Task instead. Also, combining [HttpGet] and [AcceptVerbs("Post")] is redundant; stick with [HttpPost] since your client is making POST requests.
Updated SendSOA Method
[HttpPost] public async Task SendSOA() { try { string FunctionName = "SendSOA"; DateTime AsOfDate = DateTime.Today; List<tblSMTPSetting> lstSMTPSettings = CashVitae.SMTPSetting.GetTenantSMTPsetting(); List<StatementOfAcc> lstSOA = GetStatementOfAcc(lstSMTPSettings); foreach (StatementOfAcc SOA in lstSOA) { long CompanyId = cUtil.ToLong(SOA.CompanyId); tblSMTPSetting SMTP = lstSMTPSettings.Where(x => x.CompanyId == CompanyId).FirstOrDefault(); if (SMTP != null) { long TenantId = SMTP.TenantId; string Host = SMTP.Host; int Port = SMTP.Port; string UserName = SMTP.UN; string Password = cGlobalUI.decrypt(SMTP.Pwd); // If SendSOAToCustomer is async, await it; if not, consider converting it to async await SendSOAToCustomer(TenantId, Host, UserName, Password, Port, SOA); } } } catch (Exception ex) { SendFailedEmail(FunctionName, $"Exception: {ex.Message}\nInner Exception: {ex.InnerException}", oCustomerId); } }
Updated SendNotification Method
[HttpPost] public async Task SendNotification() { List<Alert> lstAlert = GetAllNotifications(); FCM obj = new CP_UI.FCM(cGlobalUI.FCMFilePath, cGlobalUI.FCMProjectId); foreach (Alert item in lstAlert) { try { // Await if SendMessageToDevice is async; convert it if it's synchronous await obj.SendMessageToDevice(item.Title, item.MessageBody, item.DeviceToken); } catch (Exception ex) { // Don't swallow exceptions silently—add logging here! // Example: _logger.LogError(ex, "Failed to send notification to device {DeviceToken}", item.DeviceToken); } } }
Quick Notes:
- If
SendSOAToCustomerorSendMessageToDeviceare synchronous, convert them to async/await to avoid blocking controller threads. - Never swallow exceptions without logging them—you'll need that info to debug issues later.
2. Implement Fire-and-Forget in Your Desktop App
Instead of using HttpWebRequest.GetResponse() (which blocks until a full response arrives), use HttpClient with SendAsync and HttpCompletionOption.ResponseHeadersRead. This tells the client to stop waiting once the API confirms it's accepted the request, instead of waiting for the long-running operation to finish.
First, reuse a single HttpClient instance (creating a new one each time causes socket exhaustion):
// Reuse this static HttpClient instance across your app private static readonly HttpClient _httpClient = new HttpClient(); public async Task CallAPIFireAndForget() { // Fetch URLs from config string apiBaseUrl = ConfigurationManager.AppSettings["APIUrl"].ToString(); string soaBaseUrl = ConfigurationManager.AppSettings["Url"].ToString(); string sendNotificationUrl = $"{apiBaseUrl}/SendNotification"; string sendSoaUrl = $"{soaBaseUrl}/SendSOA"; // Helper method to send a fire-and-forget request async Task SendRequest(string requestUrl) { try { var request = new HttpRequestMessage(HttpMethod.Post, requestUrl); request.Content = new StringContent("", Encoding.UTF8, "application/json"); // Wait only for response headers (not the full operation) var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); // Log success/failure for debugging if (response.IsSuccessStatusCode) { Console.WriteLine($"Successfully triggered API: {requestUrl}"); } else { Console.WriteLine($"Failed to trigger API {requestUrl}: {response.StatusCode}"); } } catch (Exception ex) { Console.WriteLine($"Error triggering API {requestUrl}: {ex.Message}"); } } // Trigger both APIs in parallel (faster than sequential calls) await Task.WhenAll( SendRequest(sendNotificationUrl), SendRequest(sendSoaUrl) ); }
If You Want True Fire-and-Forget (No Waiting At All)
If you don't care whether the request succeeds or fails (not recommended unless you have no need for error tracking), you can start the tasks without awaiting them:
// Use this only if you don't need to know about failures _ = SendRequest(sendNotificationUrl); _ = SendRequest(sendSoaUrl);
Just be warned: unhandled exceptions here could crash your app, so always add logging even if you don't await the task.
内容的提问来源于stack exchange,提问作者Rakesh

