{"id":358,"date":"2022-11-02T15:10:44","date_gmt":"2022-11-02T14:10:44","guid":{"rendered":"https:\/\/blog.adameczek.pl\/?p=358"},"modified":"2026-01-07T11:46:25","modified_gmt":"2026-01-07T10:46:25","slug":"342","status":"publish","type":"post","link":"https:\/\/blog.adameczek.pl\/index.php\/2022\/11\/02\/342\/","title":{"rendered":"Kopiowanie jest OK, ale r\u00f3b to asynchronicznie"},"content":{"rendered":"\n<p>Dzi\u015b o kopiowaniu. Tyle, \u017ce nie o kopiowaniu czyjego\u015b kodu, czego nie zalecam robi\u0107 bez zrozumienia, bo mo\u017cna sobie\/klientowi\/pracodawcy zrobi\u0107 krzywd\u0119. B\u0119dzie o kopiowaniu plik\u00f3w. Kopiowanie plik\u00f3w jak wiadomo nie jest operacj\u0105, przez kt\u00f3r\u0105 mieliby\u015bmy nie spa\u0107. NET zapewnia zgrabne metody w statycznych klasach <code>File<\/code> czy <code>Directory<\/code>. Dodajemy sobie przestrze\u0144 <code>System.IO<\/code> i ju\u017c mo\u017cemy kopiowa\u0107 do woli.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism off-numbers lang-csharp\" data-lang=\"C#\"><code>File.Copy(&quot;st\u0105d&quot;, &quot;tam&quot;)<\/code><\/pre><\/div>\n\n\n\n<p>Je\u015bli chcemy by\u0107 bardziej profi, a na naszym pliku wykonujemy wi\u0119cej ni\u017c jedn\u0105 operacj\u0119, na przyk\u0142ad chcemy do jakiego\u015b pliku dopisywa\u0107 now\u0105 linijk\u0119 upami\u0119tniaj\u0105c\u0105 nowego SMSa, kt\u00f3rego Romek napisa\u0142 do Ali, to mo\u017cemy utworzy\u0107 obiekt  <code>new FileInfo(\"smsy-romka.txt\")<\/code> i u\u017cywa\u0107 go do tego celu. B\u0119dzie si\u0119 to odbywa\u0142o nieco szybciej i fajniej (czyt. profesjonalnie).<\/p>\n\n\n\n<p>Schody zaczn\u0105 si\u0119, kiedy zamiast tekstowych SMS\u00f3w b\u0119dziemy chcieli skopiowa\u0107 zdj\u0119cia wa\u017c\u0105ce po kilka MB jedno, kt\u00f3re Romek zrobi\u0142 na spacerze, a mia\u0142 Romek du\u017c\u0105 kart\u0119 pami\u0119ci i troch\u0119 czasu. Wtedy mo\u017ce to potrwa\u0107 d\u0142u\u017cej. Ca\u0142kiem nam si\u0119 humor popsuje, je\u015bli pliki trzeba b\u0119dzie przesy\u0142a\u0107 przez sie\u0107, nie w obr\u0119bie jednej maszyny. Wtedy najwolniejszym ogniwem nie b\u0119dzie nasz dysk, tylko wydajno\u015b\u0107 sieci, a z t\u0105 bywa r\u00f3\u017cnie.<\/p>\n\n\n\n<p>Takim kopiowaniem, jak pokaza\u0142em powy\u017cej zablokujesz w\u0105tek. Je\u015bli jest to w\u0105tek UI, to aplikacja stanie si\u0119 \u201etrudna w kontakcie\u201d lub inaczej m\u00f3wi\u0105c nie responsywna i mo\u017ce si\u0119 okaza\u0107, \u017ce nawet nie da si\u0119 jej zamkn\u0105\u0107 (WinForms). Sprytnie mo\u017cesz przekaza\u0107 taki przyd\u0142ugi proces do wykonania w tle w w\u0105tku pobranym z puli, a w\u0105tek g\u0142\u00f3wny mo\u017ce w tym czasie wykonywa\u0107 inne zadania, jakie dla niego wymy\u015blimy, albo obs\u0142ugiwa\u0107 interfejs u\u017cytkownika.<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism off-numbers lang-csharp\" data-lang=\"C#\"><code>var copyTask = Task.Run(() => File.Copy(&quot;st\u0105d&quot;, &quot;tam&quot;));<\/code><\/pre><\/div>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Kopiowanie plik\u00f3w wykonywane przez OS \u201epod spodem\u201d .NET jest asynchroniczne. Dlatego zatrudnienie nowego w\u0105tku niczego nie polepszy, je\u015bli Task.RunI() zastosujemy w kodzie asynchronicznym. Taki trik ma sens tylko, je\u015bli znajdzie si\u0119 w kodzie wykonywanym przez g\u0142\u00f3wny w\u0105tek aplikacji.<\/p>\n<\/blockquote>\n\n\n\n<p>Trzeba poczeka\u0107 na zako\u0144czenie tego Tasku wydaj\u0105c komend\u0119: <code>await copyTask<\/code>. Po doj\u015bciu do <code>await<\/code>, w\u0105tek g\u0142\u00f3wny przejdzie do innych zada\u0144. I je\u015bli jest to aplikacja webowa lub okienkowa, to natychmiast docenimy zalety naszego posuni\u0119cia, bo aplikacja przestanie \u201elagowa\u0107\u201d. W wypasionej wersji mo\u017cna nawet przekaza\u0107 <code>CancellationToken<\/code>, aby sko\u0144czy\u0107 z takim Taskiem, gdyby oczekiwanie znudzi\u0142o usera i postanowi\u0142 zamkn\u0105\u0107 aplikacj\u0119.<\/p>\n\n\n\n<p>Jest jednak jeden problem. Po zamkni\u0119ciu aplikacji przed zako\u0144czeniem kopiowania system plik\u00f3w zostawi nam w docelowej lokalizacji plik z przypadkow\u0105 zawarto\u015bci\u0105. Na dodatek zaalokuje miejsce na dysku. Romek nawet nie b\u0119dzie wiedzia\u0142, \u017ce straci\u0142 swoje zdj\u0119cie!<\/p>\n\n\n\n<p>Aby temat ogarn\u0105\u0107 tak, \u017ceby wstydu nie by\u0142o, wypada\u0142o by zrobi\u0107 dwie rzeczy:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Zadanie kopiowania wykona\u0107 asynchronicznie, ale tak, \u017ceby ewentualny shut down aplikcji da\u0142 nam czas na zwolnienie zasob\u00f3w i posprz\u0105tanie, zanim ostatecznie aplikacja zostanie zamkni\u0119ta.<\/li>\n\n\n\n<li>Posprz\u0105tanie pozosta\u0142o\u015bci po przerwanym zadaniu.\n\n\n\n\n\n\n\n<\/li>\n<\/ol>\n\n\n\n<p>Do pierwszego punktu u\u017cyjemy klasy <code>FileStream<\/code>, kt\u00f3ra zawiera asynchroniczn\u0105 metod\u0119 <code>CopyToAsync<\/code>. Nie b\u0119dziemy oryginalni i u\u017cyjemy tej metody. Czyli kopiujemy tak:<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism off-numbers lang-csharp\" data-lang=\"C#\"><code>async Task CopyFileAsync(string sourcePath, string destinationPath)\n{\n    var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read);\n    var destinationStream = new FileStream(destinationPath, FileMode.CreateNew, FileAccess.Write);\n    await sourceStream.CopyToAsync(destinationStream);\n}<\/code><\/pre><\/div>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Kod jest uproszczony, aby by\u0142 przejrzysty. Pe\u0142ny dzia\u0142aj\u0105cy kod mo\u017cna znale\u017a\u0107 na Githubie. Link znajduje si\u0119 pod artyku\u0142em.<\/p>\n<\/blockquote>\n\n\n\n<p>Jest prawie super, ale musimy rozwi\u0105za\u0107 temat sprz\u0105tania po ewentualnym przerwaniu kopiowania. Poprawiona wersja wygl\u0105da tak:<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-csharp\" data-lang=\"C#\"><code>async Task CopyFileAsync(string sourcePath, string destinationPath, CancellationToken cancellationToken)\n{\n    try\n    {\n        var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read);\n        var destinationStream = new FileStream(destinationPath, FileMode.CreateNew, FileAccess.Write);\n        await sourceStream.CopyToAsync(destinationStream, cancellationToken);\n    }\n    catch (OperationCanceledException)\n    {\n        var cts = new CancellationTokenSource();\n        cts.CancelAfter(TimeSpan.FromSeconds(3));\n        await Delete(destinationPath, cts.Token);\n    }\n    catch (Exception)\n    {\n        using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);\n        cts.CancelAfter(TimeSpan.FromSeconds(10));\n        await Delete(destinationPath, cts.Token);\n        throw;\n    }\n}<\/code><\/pre><\/div>\n\n\n\n<p>Po pierwsze, asynchroniczna metoda <code>CopyToAsync<\/code> przyjmuje <code>CancellationToken<\/code>, wi\u0119c korzystamy z tego natychmiast i przekazujemy do niej token (linia 7).<\/p>\n\n\n\n<p>Po drugie, w linii 9 przechwytujemy <code>OperationCanceledException<\/code> i czas jaki mamy na zamkmi\u0119cie procesu wykorzystujemy na skasowanie pliku, kt\u00f3ry zosta\u0142 utworzony w docelowej lokalizacji, ale na pewno b\u0119dzie uszkodzony, bo przecie\u017c kopiowanie zosta\u0142o przerwane. W tym celu tworzymy nowy token, kt\u00f3ry daje metodzie <code>Delete<\/code>() trzy d\u0142ugie sekundy z pi\u0119ciu jakie defaultowo dostaje aplikacja na zamkni\u0119cie (linia 12).<\/p>\n\n\n\n<p>Po trzecie, w przypadku innego b\u0142\u0119du tak\u017ce kasujemy plik, kt\u00f3ry najprawdopodobniej b\u0119dzie uszkodzony, ale dodatkowo rzucamy wyj\u0105tek do kodu wywo\u0142uj\u0105cego kopiowanie. Niech go sobie obs\u0142u\u017cy (linia 20).<\/p>\n\n\n\n<p>W ten spos\u00f3b kopiowanie nawet du\u017cych plik\u00f3w mo\u017ce odbywa\u0107 si\u0119 bez utraty responsywno\u015bci aplikacji, a ewentualne b\u0142\u0119dy nie powoduj\u0105 \u015bmietnika w systemie plik\u00f3w. Kod, kt\u00f3ry jest na <a href=\"https:\/\/github.com\/madameczek?tab=repositories\" data-type=\"URL\" data-id=\"https:\/\/github.com\/madameczek\/async-file-manipulation\/blob\/main\/AsyncFileManipulation.cs\">Githubie <\/a>dodatkowo pilnuje cyklu \u017cycia egzemplarzy <code>FileStream<\/code>. Ma te\u017c zaimplementowan\u0105 flag\u0119 overvrite i ma dodatkow\u0105 metod\u0119 <code>MoveFileAsync<\/code>.<\/p>\n\n\n\n<p>W ka\u017cdym kodzie znajdzie si\u0119 co\u015b do poprawienia. Nie inaczej jest w tym przypadku. Plik po zapisaniu w nowej lokalizacji powinien by\u0107 zweryfikowany na okoliczno\u015b\u0107 zgodno\u015bci z orygina\u0142em. Mo\u017cna to zaimplementowa\u0107 przez obliczenie skr\u00f3t\u00f3w orygina\u0142u i kopii i por\u00f3wnaniu ich warto\u015bci. Je\u015bli nie s\u0105 zgodne, to co\u015b posz\u0142o nie tak i nale\u017cy taki przypadek obs\u0142u\u017cy\u0107.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Uwaga na koniec<\/h2>\n\n\n\n<p>Z punktu widzenia aplikacji .net metoda <code>File.Copy<\/code> wykona si\u0119 synchronicznie i kod wywo\u0142uj\u0105cy <code>File.Copy<\/code> rzeczywi\u015bcie na chwil\u0119 spauzuje. Jednak operacje I\/O s\u0105 asynchroniczne na poziomie systemu operacyjnego. Co wi\u0119cej, aplikacja wykonuj\u0105ca si\u0119 pod kontrol\u0105 Windows nawet nie otrzymuje danych do kopiowania. .net przesy\u0142a do systemu informacje o pliku i reszta odbywa si\u0119 ju\u017c bardzo szybko na poziomie systemu. Zatem mo\u017cesz spokojnie stosowa\u0107 synchroniczne komendy np. <code>File.Copy<\/code> w swoim kodzie.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Kiedy wobec tego stosowa\u0107 kopiowanie asynchroniczne? <\/h2>\n\n\n\n<p>Rowa\u017c to rozwi\u0105zanie kiedy<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>pliki s\u0105 du\u017ce<\/li>\n\n\n\n<li>aplikacja ma UI i obs\u0142uga kopiowania mo\u017ce wp\u0142yn\u0105\u0107 na p\u0142ynno\u015b\u0107 interfejsu<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Przyk\u0142adowy kod<\/h2>\n\n\n\n<p>Kod ilustruj\u0105cy ten artyku\u0142 znajdziesz:<\/p>\n\n\n\n<a href=\"https:\/\/github.com\/madameczek\/async-file-manipulation\/blob\/main\/AsyncFileManipulation.cs\">Pe\u0142en kod na Githubie<\/a>\n","protected":false},"excerpt":{"rendered":"<p>O kopiowaniu plik\u00f3w, kt\u00f3rego nie zaskoczy shut down serwera.<\/p>\n","protected":false},"author":1,"featured_media":822,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ocean_post_layout":"","ocean_both_sidebars_style":"","ocean_both_sidebars_content_width":0,"ocean_both_sidebars_sidebars_width":0,"ocean_sidebar":"0","ocean_second_sidebar":"0","ocean_disable_margins":"enable","ocean_add_body_class":"","ocean_shortcode_before_top_bar":"","ocean_shortcode_after_top_bar":"","ocean_shortcode_before_header":"","ocean_shortcode_after_header":"","ocean_has_shortcode":"","ocean_shortcode_after_title":"","ocean_shortcode_before_footer_widgets":"","ocean_shortcode_after_footer_widgets":"","ocean_shortcode_before_footer_bottom":"","ocean_shortcode_after_footer_bottom":"","ocean_display_top_bar":"off","ocean_display_header":"off","ocean_header_style":"","ocean_center_header_left_menu":"0","ocean_custom_header_template":"0","ocean_custom_logo":0,"ocean_custom_retina_logo":0,"ocean_custom_logo_max_width":150,"ocean_custom_logo_tablet_max_width":150,"ocean_custom_logo_mobile_max_width":150,"ocean_custom_logo_max_height":150,"ocean_custom_logo_tablet_max_height":150,"ocean_custom_logo_mobile_max_height":150,"ocean_header_custom_menu":"0","ocean_menu_typo_font_family":"0","ocean_menu_typo_font_subset":"","ocean_menu_typo_font_size":0,"ocean_menu_typo_font_size_tablet":0,"ocean_menu_typo_font_size_mobile":0,"ocean_menu_typo_font_size_unit":"px","ocean_menu_typo_font_weight":"","ocean_menu_typo_font_weight_tablet":"","ocean_menu_typo_font_weight_mobile":"","ocean_menu_typo_transform":"","ocean_menu_typo_transform_tablet":"","ocean_menu_typo_transform_mobile":"","ocean_menu_typo_line_height":0,"ocean_menu_typo_line_height_tablet":0,"ocean_menu_typo_line_height_mobile":0,"ocean_menu_typo_line_height_unit":"","ocean_menu_typo_spacing":0,"ocean_menu_typo_spacing_tablet":0,"ocean_menu_typo_spacing_mobile":0,"ocean_menu_typo_spacing_unit":"","ocean_menu_link_color":"","ocean_menu_link_color_hover":"","ocean_menu_link_color_active":"","ocean_menu_link_background":"","ocean_menu_link_hover_background":"","ocean_menu_link_active_background":"","ocean_menu_social_links_bg":"","ocean_menu_social_hover_links_bg":"","ocean_menu_social_links_color":"","ocean_menu_social_hover_links_color":"","ocean_disable_title":"default","ocean_disable_heading":"default","ocean_post_title":"","ocean_post_subheading":".NET","ocean_post_title_style":"solid-color","ocean_post_title_background_color":"#545454","ocean_post_title_background":0,"ocean_post_title_bg_image_position":"","ocean_post_title_bg_image_attachment":"","ocean_post_title_bg_image_repeat":"","ocean_post_title_bg_image_size":"","ocean_post_title_height":0,"ocean_post_title_bg_overlay":0.5,"ocean_post_title_bg_overlay_color":"","ocean_disable_breadcrumbs":"default","ocean_breadcrumbs_color":"","ocean_breadcrumbs_separator_color":"","ocean_breadcrumbs_links_color":"","ocean_breadcrumbs_links_hover_color":"","ocean_display_footer_widgets":"default","ocean_display_footer_bottom":"default","ocean_custom_footer_template":"0","_jetpack_memberships_contains_paid_content":false,"ocean_post_oembed":"","ocean_post_self_hosted_media":"","ocean_post_video_embed":"","ocean_link_format":"","ocean_link_format_target":"self","ocean_quote_format":"","ocean_quote_format_link":"post","ocean_gallery_link_images":"off","ocean_gallery_id":[],"footnotes":""},"categories":[10],"tags":[19,18],"class_list":["post-358","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-net","tag-async-await","tag-net","entry","has-media"],"jetpack_featured_media_url":"https:\/\/blog.adameczek.pl\/wp-content\/uploads\/2024\/05\/Csharp_logo.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/posts\/358","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/comments?post=358"}],"version-history":[{"count":48,"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/posts\/358\/revisions"}],"predecessor-version":[{"id":1036,"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/posts\/358\/revisions\/1036"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/media\/822"}],"wp:attachment":[{"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/media?parent=358"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/categories?post=358"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.adameczek.pl\/index.php\/wp-json\/wp\/v2\/tags?post=358"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}