Skip to content

Commit c631981

Browse files
committed
Merge branch 'release/Version_3.0.4'
2 parents 1d8a76d + 6fec49b commit c631981

File tree

7 files changed

+68
-29
lines changed

7 files changed

+68
-29
lines changed

src/Downloader.Test/IntegrationTests/DownloadIntegrationTest.cs

+7-6
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,11 @@ public async Task StopResumeDownloadTest()
138138
downloadCompletedSuccessfully = true;
139139
}
140140
};
141-
downloader.DownloadStarted += delegate {
141+
downloader.DownloadStarted += async delegate {
142142
if (expectedStopCount > stopCount)
143143
{
144144
// Stopping after start of downloading
145-
downloader.CancelAsync();
145+
await downloader.CancelTaskAsync().ConfigureAwait(false);
146146
stopCount++;
147147
}
148148
};
@@ -255,13 +255,13 @@ public async Task StopResumeDownloadOverFirstPackagePositionTest()
255255
var isSavingStateOnCancel = false;
256256
var isSavingStateBeforCancel = false;
257257

258-
downloader.DownloadProgressChanged += (s, e) => {
258+
downloader.DownloadProgressChanged += async (s, e) => {
259259
totalReceivedBytes += e.ReceivedBytes.Length;
260260
isSavingStateBeforCancel |= downloader.Package.IsSaving;
261261
if (e.ReceivedBytesSize > stopThreshold)
262262
{
263263
// Stopping after start of downloading
264-
downloader.CancelAsync();
264+
await downloader.CancelTaskAsync().ConfigureAwait(false);
265265
stopThreshold *= 2;
266266

267267
// check point of package for once time
@@ -305,13 +305,13 @@ public async Task TestTotalReceivedBytesWhenResumeDownload()
305305
config.BufferBlockSize = 1024;
306306
config.ChunkCount = 1;
307307
var downloader = new DownloadService(config);
308-
downloader.DownloadProgressChanged += (s, e) => {
308+
downloader.DownloadProgressChanged += async (s, e) => {
309309
totalDownloadSize += e.ReceivedBytes.Length;
310310
lastProgressPercentage = e.ProgressPercentage;
311311
if (canStopDownload && totalDownloadSize > DummyFileHelper.FileSize16Kb / 2)
312312
{
313313
// Stopping after start of downloading
314-
downloader.CancelAsync();
314+
await downloader.CancelTaskAsync().ConfigureAwait(false);
315315
canStopDownload = false;
316316
}
317317
};
@@ -619,6 +619,7 @@ private async Task testRetryDownloadAfterFailure(bool timeout)
619619
Config.BufferBlockSize = 1024;
620620
Config.MinimumSizeOfChunking = 0;
621621
Config.Timeout = 100;
622+
Config.ClearPackageOnCompletionWithFailure = false;
622623
var downloadService = new DownloadService(Config);
623624
var url = timeout
624625
? DummyFileHelper.GetFileWithTimeoutAfterOffset(fileSize, failureOffset)

src/Downloader.Test/IntegrationTests/DownloadServiceTest.cs

+36-16
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,26 @@ public async Task CancelAsyncTest()
5959
Assert.AreEqual(typeof(TaskCanceledException), eventArgs.Error.GetType());
6060
}
6161

62+
[TestMethod]
63+
public async Task CancelTaskAsyncTest()
64+
{
65+
// arrange
66+
AsyncCompletedEventArgs eventArgs = null;
67+
string address = DummyFileHelper.GetFileUrl(DummyFileHelper.FileSize16Kb);
68+
Options = GetDefaultConfig();
69+
DownloadStarted += async (s, e) => await CancelTaskAsync().ConfigureAwait(false);
70+
DownloadFileCompleted += (s, e) => eventArgs = e;
71+
72+
// act
73+
await DownloadFileTaskAsync(address).ConfigureAwait(false);
74+
75+
// assert
76+
Assert.IsTrue(IsCancelled);
77+
Assert.IsNotNull(eventArgs);
78+
Assert.IsTrue(eventArgs.Cancelled);
79+
Assert.AreEqual(typeof(TaskCanceledException), eventArgs.Error.GetType());
80+
}
81+
6282
[TestMethod]
6383
[Timeout(5000)]
6484
public async Task CompletesWithErrorWhenBadUrlTest()
@@ -86,7 +106,7 @@ public async Task CompletesWithErrorWhenBadUrlTest()
86106
public async Task ClearTest()
87107
{
88108
// arrange
89-
CancelAsync();
109+
await CancelTaskAsync().ConfigureAwait(false);
90110

91111
// act
92112
await Clear();
@@ -155,9 +175,9 @@ public async Task CancelPerformanceTest()
155175
var watch = new Stopwatch();
156176
string address = DummyFileHelper.GetFileUrl(DummyFileHelper.FileSize16Kb);
157177
Options = GetDefaultConfig();
158-
DownloadProgressChanged += (s, e) => {
178+
DownloadProgressChanged += async (s, e) => {
159179
watch.Start();
160-
CancelAsync();
180+
await CancelTaskAsync();
161181
};
162182
DownloadFileCompleted += (s, e) => eventArgs = e;
163183

@@ -182,10 +202,10 @@ public async Task ResumePerformanceTest()
182202
string address = DummyFileHelper.GetFileUrl(DummyFileHelper.FileSize16Kb);
183203
Options = GetDefaultConfig();
184204
DownloadFileCompleted += (s, e) => eventArgs = e;
185-
DownloadProgressChanged += (s, e) => {
205+
DownloadProgressChanged += async (s, e) => {
186206
if (isCancelled == false)
187207
{
188-
CancelAsync();
208+
await CancelTaskAsync().ConfigureAwait(false);
189209
isCancelled = true;
190210
}
191211
else
@@ -247,11 +267,11 @@ public async Task CancelAfterPauseTest()
247267
DownloadFileCompleted += (s, e) => eventArgs = e;
248268

249269
// act
250-
DownloadProgressChanged += (s, e) => {
270+
DownloadProgressChanged += async (s, e) => {
251271
Pause();
252272
cancelStateBeforeCancel = IsCancelled;
253273
pauseStateBeforeCancel = IsPaused;
254-
CancelAsync();
274+
await CancelTaskAsync().ConfigureAwait(false);
255275
pauseStateAfterCancel = IsPaused;
256276
cancelStateAfterCancel = IsCancelled;
257277
};
@@ -308,10 +328,10 @@ public async Task ResumeNotSupportedUrlTest()
308328
var address = DummyFileHelper.GetFileWithNoAcceptRangeUrl("test.dat", DummyFileHelper.FileSize16Kb);
309329
Options = GetDefaultConfig();
310330
DownloadFileCompleted += (s, e) => eventArgs = e;
311-
DownloadProgressChanged += (s, e) => {
331+
DownloadProgressChanged += async (s, e) => {
312332
if (cancelOnProgressNo == progressCount++)
313333
{
314-
CancelAsync();
334+
await CancelTaskAsync();
315335
isCancelled = true;
316336
}
317337
else if (isCancelled)
@@ -394,11 +414,11 @@ public async Task ActiveChunksAfterCancelResumeWithNotSupportedUrlTest()
394414
var cancelOnProgressNo = 6;
395415
var address = DummyFileHelper.GetFileWithNoAcceptRangeUrl("test.dat", DummyFileHelper.FileSize16Kb);
396416
Options = GetDefaultConfig();
397-
DownloadProgressChanged += (s, e) => {
417+
DownloadProgressChanged += async (s, e) => {
398418
allActiveChunksCount.Add(e.ActiveChunks);
399419
if (cancelOnProgressNo == progressCount++)
400420
{
401-
CancelAsync();
421+
await CancelTaskAsync().ConfigureAwait(false);
402422
isCancelled = true;
403423
}
404424
else if (isCancelled)
@@ -549,11 +569,11 @@ public async Task TestResumeFromSerializedPackage(bool onMemory)
549569
var packageText = string.Empty;
550570
var url = DummyFileHelper.GetFileUrl(DummyFileHelper.FileSize16Kb);
551571
Options = GetDefaultConfig();
552-
ChunkDownloadProgressChanged += (s, e) => {
572+
ChunkDownloadProgressChanged += async (s, e) => {
553573
if (isCancelOccurred == false)
554574
{
555575
isCancelOccurred = true;
556-
CancelAsync();
576+
await CancelTaskAsync();
557577
}
558578
};
559579
DownloadFileCompleted += (s, e) => {
@@ -598,11 +618,11 @@ public async Task TestPackageStatusAfterCancellation()
598618
var completedStatus = DownloadStatus.None;
599619

600620
DownloadStarted += (s, e) => createdStatus = Package.Status;
601-
DownloadProgressChanged += (s, e) => {
621+
DownloadProgressChanged += async (s, e) => {
602622
runningStatus = Package.Status;
603623
if (e.ProgressPercentage > 50 && e.ProgressPercentage < 70)
604624
{
605-
CancelAsync();
625+
await CancelTaskAsync().ConfigureAwait(false);
606626
cancelledStatus = Package.Status;
607627
}
608628
};
@@ -644,7 +664,7 @@ public async Task TestResumeDownloadImmedietalyAfterCancellationAsync()
644664
}
645665
else if (e.ProgressPercentage > 50 && e.ProgressPercentage < 60)
646666
{
647-
CancelAsync();
667+
await CancelTaskAsync().ConfigureAwait(false);
648668
checkProgress = true;
649669
await DownloadFileTaskAsync(Package).ConfigureAwait(false);
650670
tcs.SetResult(true);

src/Downloader/Download.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public async Task<Stream> StartAsync(CancellationToken cancellationToken = defau
8484

8585
public void Stop()
8686
{
87-
downloadService.CancelAsync();
87+
downloadService.CancelTaskAsync().Wait();
8888
}
8989

9090
public void Pause()

src/Downloader/DownloadService.cs

+20-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class DownloadService : IDownloadService, IDisposable
1414
private SemaphoreSlim _parallelSemaphore;
1515
private readonly SemaphoreSlim _singleInstanceSemaphore = new SemaphoreSlim(1, 1);
1616
private CancellationTokenSource _globalCancellationTokenSource;
17+
private TaskCompletionSource<AsyncCompletedEventArgs> _taskCompletion;
1718
private readonly PauseTokenSource _pauseTokenSource;
1819
private ChunkHub _chunkHub;
1920
private Request _requestInstance;
@@ -101,6 +102,14 @@ public void CancelAsync()
101102
Status = DownloadStatus.Stopped;
102103
}
103104

105+
public async Task CancelTaskAsync()
106+
{
107+
CancelAsync();
108+
await Task.Yield(); // prevents a sync/hot thread hangup
109+
if (_taskCompletion is not null)
110+
await _taskCompletion.Task;
111+
}
112+
104113
public void Resume()
105114
{
106115
Status = DownloadStatus.Running;
@@ -118,14 +127,22 @@ public async Task Clear()
118127
try
119128
{
120129
if (IsBusy || IsPaused)
121-
CancelAsync();
130+
await CancelTaskAsync().ConfigureAwait(false);
122131

123132
await _singleInstanceSemaphore?.WaitAsync();
124133

125134
_parallelSemaphore?.Dispose();
126135
_globalCancellationTokenSource?.Dispose();
127136
_bandwidth.Reset();
128137
_requestInstance = null;
138+
139+
if (_taskCompletion is not null)
140+
{
141+
if (_taskCompletion.Task.IsCompleted == false)
142+
_taskCompletion.TrySetCanceled();
143+
144+
_taskCompletion = null;
145+
}
129146
// Note: don't clear package from `DownloadService.Dispose()`.
130147
// Because maybe it will be used at another time.
131148
}
@@ -140,6 +157,7 @@ private async Task InitialDownloader(string address, CancellationToken cancellat
140157
await Clear();
141158
Status = DownloadStatus.Created;
142159
_globalCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
160+
_taskCompletion = new TaskCompletionSource<AsyncCompletedEventArgs>();
143161
_requestInstance = new Request(address, Options.RequestConfiguration);
144162
Package.Address = _requestInstance.Address.OriginalString;
145163
_chunkHub = new ChunkHub(Options);
@@ -357,6 +375,7 @@ private void OnDownloadFileCompleted(AsyncCompletedEventArgs e)
357375
Package.Storage = null;
358376
}
359377

378+
_taskCompletion.TrySetResult(e);
360379
DownloadFileCompleted?.Invoke(this, e);
361380
}
362381

src/Downloader/Downloader.csproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22
<PropertyGroup>
33
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1;net452;net6.0;</TargetFrameworks>
44
<LangVersion>latestMajor</LangVersion>
5-
<Version>3.0.3</Version>
5+
<Version>3.0.4</Version>
66
<Title>Downloader</Title>
77
<Authors>Behzad Khosravifar</Authors>
88
<Company>bezzad</Company>
99
<Description>Fast and reliable multipart downloader with asynchronous progress events for .NET</Description>
1010
<PackageProjectUrl>https://github.com/bezzad/Downloader</PackageProjectUrl>
1111
<RepositoryUrl>https://github.com/bezzad/Downloader</RepositoryUrl>
1212
<PackageTags>download-manager, downloader, download, idm, internet, streaming, download-file, stream-downloader, multipart-download</PackageTags>
13-
<PackageReleaseNotes>Fixed serialize download package after cancelled or failed. #129</PackageReleaseNotes>
13+
<PackageReleaseNotes>Added task async method for the download cancel operation. #133</PackageReleaseNotes>
1414
<SignAssembly>true</SignAssembly>
1515
<AssemblyOriginatorKeyFile>Downloader.snk</AssemblyOriginatorKeyFile>
1616
<Copyright>Copyright (C) 2019-2022 Behzad Khosravifar</Copyright>

src/Downloader/IDownloadService.cs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public interface IDownloadService
2323
Task DownloadFileTaskAsync(string address, string fileName, CancellationToken cancellationToken = default);
2424
Task DownloadFileTaskAsync(string address, DirectoryInfo folder, CancellationToken cancellationToken = default);
2525
void CancelAsync();
26+
Task CancelTaskAsync();
2627
void Pause();
2728
void Resume();
2829
Task Clear();

src/Samples/Downloader.Sample/Program.cs

+1-3
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,9 @@ private static void KeyboardHandler()
9191
case ConsoleKey.Escape:
9292
CurrentDownloadService?.CancelAsync();
9393
break;
94-
9594
case ConsoleKey.UpArrow:
9695
CurrentDownloadConfiguration.MaximumBytesPerSecond *= 2;
9796
break;
98-
9997
case ConsoleKey.DownArrow:
10098
CurrentDownloadConfiguration.MaximumBytesPerSecond /= 2;
10199
break;
@@ -256,7 +254,7 @@ private static void OnDownloadFileCompleted(object sender, AsyncCompletedEventAr
256254
ConsoleProgress.Message += " DONE";
257255
Console.Title = "100%";
258256
}
259-
257+
260258
foreach (var child in ChildConsoleProgresses.Values)
261259
child.Dispose();
262260

0 commit comments

Comments
 (0)