You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#异步线程改造市场API机器人:解决阻塞与并发买卖需求

如何将商品买卖机器人的阻塞同步方法改为异步并发执行?

我开发了一款类似商品买卖市场机器人的程序,通过在线API发送/接收POST请求与响应,功能正常但运行时会阻塞当前线程,导致无法操作程序其他功能。我希望将AttachAndBuy、buyOrder函数改为异步执行,未来还需添加可并发执行的卖出功能,相关代码如下:

private void RunBot() {
 numToBuy = (int)nudNumToBuy.Value;
 for (int i = 0; i < numToBuy; i++) {
 while (true) {
 if (AttachAndBuy()) {
 break;
 }
 }
 }
}
private bool AttachAndBuy() {
 string data = "<DATA HERE>";
 string URL = "<URL HERE>";
 Cookies.SetCookies(cookie, "PHPSESSID=" + SessionIDTextBox.Text.Replace("PHPSESSID=", ""));
 HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(URL);
 Request.ContentType = "application/json";
 Request.Accept = "*/*";
 Request.CookieContainer = Cookies;
 Request.Host = "<HOST HERE>";
 Request.Method = "POST";
 Request.UserAgent = "<USER AGENT HERE>";
 Request.Headers.Add("<HEADER>", "<VALUE>");
 Request.KeepAlive = true;
 byte[] CompressedRequest = SimpleZlib.CompressToBytes(Encoding.UTF8.GetBytes(data), 9);
 Stream RequestStream = Request.GetRequestStream();
 RequestStream.Write(CompressedRequest, 0, CompressedRequest.Length);
 RequestStream.Flush();
 Stream CompressedResponseStream = Request.GetResponse().GetResponseStream();
 byte[] CompressedResponseData = ReadToEnd(CompressedResponseStream);
 string DecompressedResponseData = SimpleZlib.Decompress(CompressedResponseData, null);
 OffersResponse Return = Json.Deserialize<OffersResponse>(DecompressedResponseData);
 int LowestCost = 1000000000;
 Offer BestOffer = new Offer();
 foreach (Offer CurrentOffer in Return.data.offers) {
 bool moneyOffer = false;
 int Costs = CurrentOffer.requirementsCost;
 string id = CurrentOffer._id;
 foreach (Requirement CurrentRequirement in CurrentOffer.requirements) {
 if (CurrentRequirement._tpl == "<TEMPLATE ID HERE>") {
 moneyOffer = true;
 }
 }
 if (moneyOffer == false) {
 continue;
 }
 if (Costs < LowestCost) {
 LowestCost = Costs;
 BestOffer = CurrentOffer;
 }
 }
 BestOfferID = BestOffer._id;
 BestOfferCost = LowestCost;
 string MoneyID = getStack(BestOfferCost);
 while (true) {
 BuyRequestAttemptCounter++;
 if (LowestCost > 140000) {
 AddLog("No Suitable Item! Skipping! Lowest Item Cost: " + LowestCost.ToString());
 return false;
 } else AddLog("Best Item Cost: " + LowestCost.ToString() + " | ID: " + BestOfferID);
 int Result = buyOrder(MoneyID);
 if (Result == 0) {
 //log info for averaging
 numberPurchased++;
 TotalCost += BestOfferCost;
 averageCost = TotalCost / numberPurchased;
 lblNumPurchased.Text = numberPurchased.ToString();
 lblAverageCost.Text = averageCost.ToString();
 lstPricesPurchased.Items.Add(LowestCost.ToString());
 AddLog("====================================");
 AddLog("Number Purchased: " + numberPurchased);
 AddLog("Average Cost: " + averageCost);
 AddLog("====================================");
 System.Media.SystemSounds.Exclamation.Play();
 return true;
 } else if (Result == 1) return false;
 else if (Result == 2) continue;
 else return false;
 }
}
private int buyOrder(string MoneyID) {
 string data = "<DATA HERE>";
 string URL = "<URL HERE>";
 Cookies.SetCookies(cookie, "PHPSESSID=" + SessionIDTextBox.Text.Replace("PHPSESSID=", ""));
 HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(URL);
 Request.ContentType = "application/json";
 Request.Accept = "*/*";
 Request.CookieContainer = Cookies;
 Request.Host = "<HOST HERE>";
 Request.Method = "POST";
 Request.UserAgent = "<USER AGENT HERE>";
 Request.Headers.Add("<HEADER>", "<VALUE>");
 Request.KeepAlive = true;
 byte[] CompressedRequest = SimpleZlib.CompressToBytes(Encoding.UTF8.GetBytes(data), 9);
 Stream RequestStream = Request.GetRequestStream();
 RequestStream.Write(CompressedRequest, 0, CompressedRequest.Length);
 RequestStream.Flush();
 Stream CompressedResponseStream = Request.GetResponse().GetResponseStream();
 byte[] CompressedResponseData = ReadToEnd(CompressedResponseStream);
 string DecompressedResponseData = SimpleZlib.Decompress(CompressedResponseData, null);
 ResponseRoot Return = Json.Deserialize<ResponseRoot>(DecompressedResponseData);
 string returnErrorCode = DecompressedResponseData.ToString();
 //AddLog(DecompressedResponseData);
 if (Return.err == 0 && returnErrorCode.Contains("id")) {
 System.Windows.Forms.Clipboard.SetText(DecompressedResponseData);
 //AddLog("Successful Purchase!");
 return 0;
 } else if (returnErrorCode.Contains("1503")) {
 //AddLog("Failed with 1503!");
 return 1;
 } else if (returnErrorCode.Contains("1512")) {
 // AddLog("Failed with 1512!");
 return 2;
 }
 return 3;
}

当然可以实现!你遇到的阻塞问题本质是同步IO操作占满了UI线程,改用C#的async/await异步模型就能轻松解决,还能为后续的并发卖出功能打好基础。下面是完整的改造方案:

核心思路:用异步HTTP客户端替代同步请求,释放UI线程

首先推荐把老旧的HttpWebRequest换成HttpClient——它原生支持异步,代码更简洁,还能复用连接池,适合频繁API调用的场景。

1. 初始化全局HttpClient实例

在窗体类里定义一个静态的HttpClient,避免每次请求都创建新实例(会导致端口耗尽):

private static readonly HttpClient _httpClient = new HttpClient();

public YourFormName()
{
    InitializeComponent();
    // 预先设置通用请求头和配置
    _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("*/*"));
    _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("<USER AGENT HERE>");
    _httpClient.DefaultRequestHeaders.Add("<HEADER>", "<VALUE>");
    _httpClient.DefaultRequestHeaders.Host = "<HOST HERE>";
}

2. 把buyOrder改成异步方法

将所有同步IO操作替换为异步版本,返回Task<int>:

private async Task<int> buyOrderAsync(string moneyID)
{
    string data = "<DATA HERE>";
    string url = "<URL HERE>";

    // 更新Cookie(如果SessionID可能动态变化,这里要每次都设置)
    _httpClient.DefaultRequestHeaders.Remove("Cookie");
    _httpClient.DefaultRequestHeaders.Add("Cookie", $"PHPSESSID={SessionIDTextBox.Text.Replace("PHPSESSID=", "")}");

    // 压缩请求数据
    byte[] compressedRequest = SimpleZlib.CompressToBytes(Encoding.UTF8.GetBytes(data), 9);
    using var content = new ByteArrayContent(compressedRequest);
    content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

    try
    {
        using var response = await _httpClient.PostAsync(url, content);
        response.EnsureSuccessStatusCode(); // 自动抛出HTTP错误状态码异常

        // 读取并解压响应
        byte[] compressedResponse = await response.Content.ReadAsByteArrayAsync();
        string decompressedResponse = SimpleZlib.Decompress(compressedResponse, null);
        ResponseRoot returnObj = Json.Deserialize<ResponseRoot>(decompressedResponse);

        // 判断返回结果
        if (returnObj.err == 0 && decompressedResponse.Contains("id"))
        {
            Clipboard.SetText(decompressedResponse);
            return 0;
        }
        else if (decompressedResponse.Contains("1503"))
        {
            return 1;
        }
        else if (decompressedResponse.Contains("1512"))
        {
            return 2;
        }
        return 3;
    }
    catch (HttpRequestException ex)
    {
        AddLog($"购买请求出错: {ex.Message}");
        return 3;
    }
}

3. 改造AttachAndBuy为异步方法

同样改成Task<bool>,内部调用异步的buyOrderAsync,还要注意UI线程安全——异步线程不能直接操作UI控件,必须用Invoke切换到UI线程:

private async Task<bool> AttachAndBuyAsync()
{
    string data = "<DATA HERE>";
    string url = "<URL HERE>";

    // 更新Cookie
    _httpClient.DefaultRequestHeaders.Remove("Cookie");
    _httpClient.DefaultRequestHeaders.Add("Cookie", $"PHPSESSID={SessionIDTextBox.Text.Replace("PHPSESSID=", "")}");

    byte[] compressedRequest = SimpleZlib.CompressToBytes(Encoding.UTF8.GetBytes(data), 9);
    using var content = new ByteArrayContent(compressedRequest);
    content.Headers.ContentType = new MediaTypeHeaderValue("application/json");

    try
    {
        using var response = await _httpClient.PostAsync(url, content);
        response.EnsureSuccessStatusCode();

        byte[] compressedResponse = await response.Content.ReadAsByteArrayAsync();
        string decompressedResponse = SimpleZlib.Decompress(compressedResponse, null);
        OffersResponse returnObj = Json.Deserialize<OffersResponse>(decompressedResponse);

        // 筛选最优报价
        int lowestCost = int.MaxValue;
        Offer bestOffer = null;
        foreach (var currentOffer in returnObj.data.offers)
        {
            // 简化判断逻辑,用LINQ替代嵌套循环
            bool isMoneyOffer = currentOffer.requirements.Any(req => req._tpl == "<TEMPLATE ID HERE>");
            if (!isMoneyOffer) continue;

            if (currentOffer.requirementsCost < lowestCost)
            {
                lowestCost = currentOffer.requirementsCost;
                bestOffer = currentOffer;
            }
        }

        if (bestOffer == null)
        {
            AddLog("未找到符合条件的商品");
            return false;
        }

        BestOfferID = bestOffer._id;
        BestOfferCost = lowestCost;
        string moneyID = getStack(lowestCost);

        if (lowestCost > 140000)
        {
            AddLog($"无合适商品!跳过!最低价格: {lowestCost}");
            return false;
        }
        AddLog($"最优商品价格: {lowestCost} | ID: {BestOfferID}");

        // 循环尝试购买
        while (true)
        {
            BuyRequestAttemptCounter++;
            int result = await buyOrderAsync(moneyID);
            
            switch (result)
            {
                case 0:
                    // 必须用Invoke切换到UI线程更新控件
                    Invoke((Action)(() =>
                    {
                        numberPurchased++;
                        TotalCost += lowestCost;
                        averageCost = TotalCost / numberPurchased;
                        lblNumPurchased.Text = numberPurchased.ToString();
                        lblAverageCost.Text = averageCost.ToString();
                        lstPricesPurchased.Items.Add(lowestCost.ToString());
                    }));
                    
                    AddLog("====================================");
                    AddLog($"已购买数量: {numberPurchased}");
                    AddLog($"平均成本: {averageCost}");
                    AddLog("====================================");
                    SystemSounds.Exclamation.Play();
                    return true;
                case 1:
                    return false;
                case 2:
                    continue;
                default:
                    return false;
            }
        }
    }
    catch (HttpRequestException ex)
    {
        AddLog($"获取报价请求出错: {ex.Message}");
        return false;
    }
}

4. 改造RunBot为异步启动方法

原来的同步RunBot会阻塞UI线程,改成异步后可以选择串行或并发执行购买任务:

// 假设这是按钮点击事件(替换原来的RunBot调用)
private async void btnStartBot_Click(object sender, EventArgs e)
{
    // 禁用按钮防止重复触发
    btnStartBot.Enabled = false;
    try
    {
        numToBuy = (int)nudNumToBuy.Value;
        
        // 方式1:串行购买(一个接一个,保持原有逻辑但不阻塞UI)
        for (int i = 0; i < numToBuy; i++)
        {
            bool success = false;
            while (!success)
            {
                success = await AttachAndBuyAsync();
            }
        }

        // 方式2:并发购买(同时执行多个任务,适合快速批量操作)
        // List<Task> purchaseTasks = new List<Task>();
        // for (int i = 0; i < numToBuy; i++)
        // {
        //     purchaseTasks.Add(Task.Run(async () =>
        //     {
        //         bool success = false;
        //         while (!success)
        //         {
        //             success = await AttachAndBuyAsync();
        //         }
        //     }));
        // }
        // await Task.WhenAll(purchaseTasks);
    }
    finally
    {
        // 无论成功失败都恢复按钮状态
        btnStartBot.Enabled = true;
    }
}

5. 未来添加并发卖出功能的注意事项

  • 卖出方法的改造逻辑和购买完全一致,改成async Task即可
  • 用Task.WhenAll或.NET 6+的Parallel.ForEachAsync实现并发卖出
  • 线程安全:如果多个任务同时修改共享变量(比如卖出数量、收益),要用lock或者Interlocked类保证原子操作
  • UI更新:所有UI操作必须通过Invoke切换到UI线程,避免跨线程异常

内容的提问来源于stack exchange,提问作者Ignition K6

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:14:24