Skip to content

Commit 3c71f33

Browse files
committed
优化选课算法
1 parent ff6733e commit 3c71f33

File tree

1 file changed

+78
-100
lines changed

1 file changed

+78
-100
lines changed

iCourse/Helpers/JLUiCourseApi.cs

Lines changed: 78 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using iCourse.ViewModels;
55
using Microsoft.Extensions.DependencyInjection;
66
using Newtonsoft.Json.Linq;
7+
using System.Collections.Concurrent;
78
using System.Diagnostics;
89
using System.Net.Http;
910
using System.Text;
@@ -273,144 +274,121 @@ private async Task<List<Course>> GetFavoriteCoursesAsync()
273274
}
274275

275276
private static readonly Random _random = new();
277+
private readonly ConcurrentQueue<(Course course, TaskCompletionSource<(bool, string?)> tcs)> _taskQueue = new();
278+
private readonly int _workerCount = Environment.ProcessorCount * 2;
279+
private readonly CancellationTokenSource _cts = new();
276280

277-
private async Task<(bool isSuccess, string? msg)> TrySelectCourseOnceAsync(Course courseInfo)
281+
private string BuildRequestBody(Course course)
278282
{
279-
client.SetReferer($"https://icourses.jlu.edu.cn/xsxk/elective/grablessons?batchId={batch.batchId}");
283+
return $"clazzId={Uri.EscapeDataString(course.CourseId)}&secretVal={Uri.EscapeDataString(course.SecretVal)}&clazzType={Uri.EscapeDataString(course.SelectType.ToString())}";
284+
}
280285

281-
try
282-
{
283-
var content = new FormUrlEncodedContent(new Dictionary<string, string>
286+
public void StartWorkerPool()
284287
{
285-
{ "clazzId", courseInfo.CourseId },
286-
{ "secretVal", courseInfo.SecretVal },
287-
{ "clazzType", courseInfo.SelectType.ToString() }
288-
});
289-
290-
var response = await client.HttpPostAsync("xsxk/sc/clazz/addxk", content);
291-
var json = JObject.Parse(response);
292-
var code = json["code"].ToObject<int>();
293-
var msg = json["msg"].ToString();
294-
295-
if (code == 200)
296-
{
297-
MessageBox.Show(msg);
298-
Logger.WriteLine("已选课程: " + courseInfo.Name);
299-
return (true, null);
300-
}
301-
302-
if (msg == "该课程已在选课结果中")
303-
{
304-
Logger.WriteLine($"{courseInfo.Name} : 已放弃,尝试选下一门课程");
305-
return (true, null);
306-
}
307-
308-
if (msg == "课容量已满")
309-
{
310-
Logger.WriteLine($"{courseInfo.Name} : 容量已满,放弃");
311-
return (false, msg);
312-
}
313-
314-
Logger.WriteLine($"{courseInfo.Name} : {msg}");
315-
return (false, msg);
316-
}
317-
catch (Exception ex)
288+
for (int i = 0; i < _workerCount; i++)
318289
{
319-
Logger.WriteLine($"{courseInfo.Name} : 异常 -> {ex.Message}");
320-
return (false, ex.Message);
290+
Task.Factory.StartNew(async () =>
291+
{
292+
while (!_cts.IsCancellationRequested)
293+
{
294+
if (_taskQueue.TryDequeue(out var workItem))
295+
{
296+
var (course, tcs) = workItem;
297+
var result = await TrySelectCourseWithBackoffAsync(course);
298+
tcs.TrySetResult(result);
299+
}
300+
else
301+
{
302+
// 无任务时等待
303+
await Task.Delay(10);
304+
}
305+
}
306+
}, TaskCreationOptions.LongRunning);
321307
}
322308
}
323309

324-
private async Task<(bool isSuccess, string? msg)> SelectCourseConcurrentlyAsync(Course courseInfo, int parallelCount = 3)
310+
private async Task<(bool isSuccess, string? msg)> TrySelectCourseWithBackoffAsync(Course course)
325311
{
326-
var cts = new CancellationTokenSource();
312+
client.DefaultRequestHeaders.Referrer = new Uri($"https://icourses.jlu.edu.cn/xsxk/elective/grablessons?batchId={batch.batchId}");
327313

328-
var tasks = new List<Task<(bool isSuccess, string? msg)>>();
314+
int failCount = 0;
329315

330-
object lockObj = new();
316+
string requestBody = BuildRequestBody(course);
317+
var content = new StringContent(requestBody, Encoding.UTF8, "application/x-www-form-urlencoded");
331318

332-
(bool isSuccess, string? msg) finalResult = (false, null);
333-
bool completed = false;
334-
335-
for (int i = 0; i < parallelCount; i++)
319+
while (true)
336320
{
337-
tasks.Add(Task.Run(async () =>
321+
try
338322
{
339-
while (!cts.IsCancellationRequested && !completed)
323+
var response = await client.PostAsync("xsxk/sc/clazz/addxk", content);
324+
var respStr = await response.Content.ReadAsStringAsync();
325+
326+
var json = JObject.Parse(respStr);
327+
var code = json["code"].ToObject<int>();
328+
var msg = json["msg"].ToString();
329+
330+
if (code == 200)
340331
{
341-
var result = await TrySelectCourseOnceAsync(courseInfo);
332+
Logger.WriteLine($"成功选课: {course.Name}");
333+
return (true, null);
334+
}
342335

343-
if (result.isSuccess)
344-
{
345-
lock (lockObj)
346-
{
347-
if (!completed)
348-
{
349-
completed = true;
350-
finalResult = result;
351-
cts.Cancel();
352-
}
353-
}
354-
break;
355-
}
356-
else if (result.msg == "课容量已满" || result.msg == "该课程已在选课结果中")
357-
{
358-
lock (lockObj)
359-
{
360-
if (!completed)
361-
{
362-
completed = true;
363-
finalResult = result;
364-
cts.Cancel();
365-
}
366-
}
367-
break;
368-
}
336+
Logger.WriteLine($"{course.Name} : {msg}");
369337

370-
// 避免所有线程同时请求
371-
await Task.Delay(30 + _random.Next(0, 30));
338+
if (msg == "该课程已在选课结果中" || msg == "课容量已满")
339+
{
340+
return (msg == "课容量已满" ? (false, msg) : (true, null));
372341
}
373342

374-
return finalResult;
375-
}));
376-
}
377343

378-
await Task.WhenAll(tasks);
344+
failCount++;
345+
int delay = Math.Min(500, 100 + failCount * 100 + _random.Next(0, 50));
346+
await Task.Delay(delay);
347+
}
348+
catch (Exception ex)
349+
{
350+
Logger.WriteLine($"{course.Name} : 异常 {ex.Message}");
351+
failCount++;
352+
int delay = Math.Min(500, 100 + failCount * 100 + _random.Next(0, 50));
353+
await Task.Delay(delay);
354+
}
355+
}
356+
}
379357

380-
return finalResult;
358+
public async Task<(bool isSuccess, string? msg)> EnqueueSelectCourseAsync(Course course)
359+
{
360+
var tcs = new TaskCompletionSource<(bool, string?)>(TaskCreationOptions.RunContinuationsAsynchronously);
361+
_taskQueue.Enqueue((course, tcs));
362+
return await tcs.Task;
381363
}
382364

383-
public async void StartSelectClassAsync()
365+
public async Task StartSelectClassAsync()
384366
{
385367
var list = await GetFavoriteCoursesAsync();
386368

387-
int totalTasks = list.Count;
388-
int completedTasks = 0;
369+
StartWorkerPool();
370+
371+
int total = list.Count;
372+
int completed = 0;
389373

390374
var tasks = list.Select(async course =>
391375
{
392-
// 3 个并发任务
393-
var (isSuccess, msg) = await SelectCourseConcurrentlyAsync(course, parallelCount: 3);
394-
395-
int currentCompleted = Interlocked.Increment(ref completedTasks);
396-
WeakReferenceMessenger.Default.Send(new SelectCourseFinishedMessage(currentCompleted, totalTasks));
397-
376+
var (isSuccess, msg) = await EnqueueSelectCourseAsync(course);
377+
Interlocked.Increment(ref completed);
378+
WeakReferenceMessenger.Default.Send(new SelectCourseFinishedMessage(completed, total));
398379
return new { course.Name, isSuccess, msg };
399380
}).ToList();
400381

401382
var results = await Task.WhenAll(tasks);
402383

403384
Logger.WriteLine("选课完成!");
404385

405-
var failedCourses = results.Where(r => !r.isSuccess).ToList();
406-
var succeeded = results.Count(r => r.isSuccess);
386+
foreach (var r in results.Where(r => !r.isSuccess))
387+
Logger.WriteLine($"课程失败: {r.Name}, 原因: {r.msg}");
407388

408-
foreach (var r in failedCourses)
409-
{
410-
Logger.WriteLine($"课程选择失败: {r.Name}, 原因: {r.msg}");
411-
}
389+
Logger.WriteLine($"成功数: {results.Count(r => r.isSuccess)}");
412390

413-
Logger.WriteLine($"成功选择课程数: {succeeded}");
391+
_cts.Cancel();
414392
}
415393

416394
private void KeepOnline()

0 commit comments

Comments
 (0)