ฉันชอบคำถามนี้มาก:
วิธีที่ง่ายที่สุดในการจุดไฟและวิธีการลืมใน C #?
ฉันแค่อยากรู้ว่าตอนนี้เรามีส่วนขยายแบบขนานใน C # 4.0 แล้วมีวิธีที่ดีกว่าในการทำ Fire & Forget ด้วย Parallel linq หรือไม่?
ฉันชอบคำถามนี้มาก:
วิธีที่ง่ายที่สุดในการจุดไฟและวิธีการลืมใน C #?
ฉันแค่อยากรู้ว่าตอนนี้เรามีส่วนขยายแบบขนานใน C # 4.0 แล้วมีวิธีที่ดีกว่าในการทำ Fire & Forget ด้วย Parallel linq หรือไม่?
คำตอบ:
ไม่ใช่คำตอบสำหรับ 4.0 แต่น่าสังเกตว่าใน. Net 4.5 คุณสามารถทำให้สิ่งนี้ง่ายขึ้นด้วย:
#pragma warning disable 4014
Task.Run(() =>
{
MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014
pragma คือการปิดใช้งานคำเตือนที่บอกคุณว่าคุณกำลังเรียกใช้งานนี้เป็นไฟและลืม
หากวิธีการภายในวงเล็บปีกกาส่งคืนงาน:
#pragma warning disable 4014
Task.Run(async () =>
{
await MyFireAndForgetMethod();
}).ConfigureAwait(false);
#pragma warning restore 4014
มาทำลายมัน:
Task.Run ส่งคืนงานซึ่งสร้างคำเตือนคอมไพเลอร์ (คำเตือน CS4014) โดยสังเกตว่าโค้ดนี้จะทำงานในพื้นหลังนั่นคือสิ่งที่คุณต้องการดังนั้นเราจึงปิดการเตือน 4014
โดยค่าเริ่มต้น Tasks จะพยายาม "Marshal กลับสู่เธรดเดิม" ซึ่งหมายความว่างานนี้จะทำงานในพื้นหลังจากนั้นพยายามกลับไปที่เธรดที่เริ่มต้น มักจะเริ่มทำงานและลืม Tasks เสร็จสิ้นหลังจากเธรดเดิมเสร็จสิ้น ซึ่งจะทำให้ ThreadAbortException ถูกโยน ในกรณีส่วนใหญ่สิ่งนี้ไม่เป็นอันตราย - แค่บอกคุณว่าฉันพยายามเข้าร่วมอีกครั้งฉันล้มเหลว แต่คุณไม่สนใจอยู่ดี แต่ก็ยังมีเสียงดังเล็กน้อยที่จะมี ThreadAbortExceptions ทั้งในบันทึกของคุณในการผลิตหรือในดีบักเกอร์ของคุณใน dev ในเครื่อง .ConfigureAwait(false)
เป็นเพียงวิธีการรักษาความเป็นระเบียบเรียบร้อยและพูดอย่างชัดเจนเรียกใช้สิ่งนี้ในพื้นหลังเท่านี้เอง
เนื่องจากสิ่งนี้เป็นคำพูดโดยเฉพาะ pragma ที่น่าเกลียดฉันจึงใช้วิธีไลบรารีสำหรับสิ่งนี้:
public static class TaskHelper
{
/// <summary>
/// Runs a TPL Task fire-and-forget style, the right way - in the
/// background, separate from the current thread, with no risk
/// of it trying to rejoin the current thread.
/// </summary>
public static void RunBg(Func<Task> fn)
{
Task.Run(fn).ConfigureAwait(false);
}
/// <summary>
/// Runs a task fire-and-forget style and notifies the TPL that this
/// will not need a Thread to resume on for a long time, or that there
/// are multiple gaps in thread use that may be long.
/// Use for example when talking to a slow webservice.
/// </summary>
public static void RunBgLong(Func<Task> fn)
{
Task.Factory.StartNew(fn, TaskCreationOptions.LongRunning)
.ConfigureAwait(false);
}
}
การใช้งาน:
TaskHelper.RunBg(async () =>
{
await doSomethingAsync();
}
ConfigureAwait(false)
ในTask.Run
เมื่อคุณทำไม่ได้await
งาน วัตถุประสงค์ของฟังก์ชั่นอยู่ในชื่อ: "กำหนดค่าawait " หากคุณไม่ได้await
ทำงานคุณจะไม่ได้ลงทะเบียนความต่อเนื่องและไม่มีรหัสสำหรับงานในการ "กลับสู่เธรดเดิม" อย่างที่คุณพูด ความเสี่ยงที่ใหญ่กว่าคือข้อยกเว้นที่ไม่สามารถสังเกตได้ซึ่งจะถูกเปลี่ยนใหม่ในเธรด Finalizer ซึ่งคำตอบนี้ไม่ได้ระบุไว้ด้วยซ้ำ
#Disable Warning BC42358
ด้วยTask
คลาสใช่ แต่ PLINQ มีไว้สำหรับการค้นหาคอลเลกชัน
สิ่งต่อไปนี้จะทำกับงาน
Task.Factory.StartNew(() => FireAway());
หรือแม้กระทั่ง...
Task.Factory.StartNew(FireAway);
หรือ...
new Task(FireAway).Start();
ที่ไหนFireAway
เป็น
public static void FireAway()
{
// Blah...
}
ดังนั้นด้วยความเที่ยงตรงของคลาสและชื่อเมธอดสิ่งนี้จะทำให้เวอร์ชันเธรดพูลอยู่ระหว่างหกถึงสิบเก้าอักขระขึ้นอยู่กับตัวที่คุณเลือก :)
ThreadPool.QueueUserWorkItem(o => FireAway());
fire and forget in ASP.NET WebForms and windows.close()
?
ฉันมีปัญหาสองสามข้อเกี่ยวกับคำตอบหลักของคำถามนี้
ครั้งแรกในจริงไฟและลืมสถานการณ์ที่คุณอาจจะไม่ได้งานจึงไม่มีประโยชน์ที่จะผนวกawait
ConfigureAwait(false)
หากคุณไม่ได้await
รับค่าที่ส่งคืนมาConfigureAwait
ก็จะไม่มีผลใด ๆ
ประการที่สองคุณต้องระวังว่าจะเกิดอะไรขึ้นเมื่องานเสร็จสมบูรณ์โดยมีข้อยกเว้น ลองพิจารณาวิธีง่ายๆที่ @ ade-miller แนะนำ:
Task.Factory.StartNew(SomeMethod); // .NET 4.0
Task.Run(SomeMethod); // .NET 4.5
สิ่งนี้ทำให้เกิดอันตราย: หากข้อยกเว้นที่ไม่สามารถจัดการได้หลุดรอดSomeMethod()
ข้อยกเว้นนั้นจะไม่ถูกสังเกตและ1อาจถูกเปลี่ยนใหม่ในเธรด Finalizer ทำให้แอปพลิเคชันของคุณหยุดทำงาน ฉันจึงขอแนะนำให้ใช้วิธีการช่วยเหลือเพื่อให้แน่ใจว่ามีการปฏิบัติตามข้อยกเว้นที่เกิดขึ้น
คุณสามารถเขียนสิ่งนี้:
public static class Blindly
{
private static readonly Action<Task> DefaultErrorContinuation =
t =>
{
try { t.Wait(); }
catch {}
};
public static void Run(Action action, Action<Exception> handler = null)
{
if (action == null)
throw new ArgumentNullException(nameof(action));
var task = Task.Run(action); // Adapt as necessary for .NET 4.0.
if (handler == null)
{
task.ContinueWith(
DefaultErrorContinuation,
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.OnlyOnFaulted);
}
else
{
task.ContinueWith(
t => handler(t.Exception.GetBaseException()),
TaskContinuationOptions.ExecuteSynchronously |
TaskContinuationOptions.OnlyOnFaulted);
}
}
}
การนำไปใช้งานนี้ควรมีค่าใช้จ่ายน้อยที่สุด: ความต่อเนื่องจะถูกเรียกใช้ก็ต่อเมื่องานไม่เสร็จสมบูรณ์และควรเรียกใช้แบบซิงโครนัส (ตรงข้ามกับการกำหนดเวลาแยกจากงานดั้งเดิม) ในกรณี "ขี้เกียจ" คุณจะไม่ได้รับการจัดสรรสำหรับผู้รับมอบสิทธิ์ต่อเนื่อง
การเริ่มต้นการดำเนินการแบบอะซิงโครนัสจะกลายเป็นเรื่องเล็กน้อย:
Blindly.Run(SomeMethod); // Ignore error
Blindly.Run(SomeMethod, e => Log.Warn("Whoops", e)); // Log error
1. นี่เป็นลักษณะการทำงานเริ่มต้นใน. NET 4.0 ใน. NET 4.5 ลักษณะการทำงานเริ่มต้นมีการเปลี่ยนแปลงเพื่อให้ข้อยกเว้นที่ไม่สามารถสังเกตได้จะไม่ถูกเปลี่ยนใหม่ในเธรด Finalizer (แม้ว่าคุณจะยังคงสังเกตเห็นได้จากเหตุการณ์ UnobservedTaskException บน TaskScheduler) อย่างไรก็ตามการกำหนดค่าเริ่มต้นสามารถถูกแทนที่ได้และแม้ว่าแอปพลิเคชันของคุณจะต้องใช้. NET 4.5 แต่คุณก็ไม่ควรคิดว่าข้อยกเว้นของงานที่ไม่ได้รับการตรวจจับจะไม่เป็นอันตราย
ContinueWith
ถูกเรียกใช้เนื่องจากการยกเลิกคุณสมบัติข้อยกเว้นของก่อนหน้านี้จะเป็น Null และข้อยกเว้นการอ้างอิงที่เป็นโมฆะจะถูกโยนทิ้ง การตั้งค่าตัวเลือกความต่อเนื่องของงานเป็นOnlyOnFaulted
จะขจัดความจำเป็นในการตรวจสอบค่าว่างหรือตรวจสอบว่าข้อยกเว้นเป็นโมฆะหรือไม่ก่อนใช้งาน
Task
จะกลายเป็นรายละเอียดการใช้งาน
Task
" ไม่เหมือนกับ "ฉันต้องการวิธีง่ายๆในการเริ่มต้นการทำงานเบื้องหลังไม่เคยสังเกตผลลัพธ์ของแอปและฉันไม่สนใจว่าจะใช้กลไกใด ที่จะทำ " บนพื้นฐานที่ว่าผมยืนอยู่เบื้องหลังคำตอบของฉันกับคำถามที่ถูกถาม
fire and forget
ในASP.NET WebFormsและwindows.close()
?
เพื่อแก้ไขปัญหาบางอย่างที่จะเกิดขึ้นกับคำตอบของ Mike Strobel:
หากคุณใช้var task = Task.Run(action)
และหลังจากกำหนดความต่อเนื่องให้กับงานนั้นคุณจะเสี่ยงต่อการTask
ทิ้งข้อยกเว้นก่อนที่คุณจะกำหนดความต่อเนื่องของตัวจัดการข้อยกเว้นให้กับไฟล์Task
. ดังนั้นชั้นเรียนด้านล่างควรไม่มีความเสี่ยงนี้:
using System;
using System.Threading.Tasks;
namespace MyNameSpace
{
public sealed class AsyncManager : IAsyncManager
{
private Action<Task> DefaultExeptionHandler = t =>
{
try { t.Wait(); }
catch { /* Swallow the exception */ }
};
public Task Run(Action action, Action<Exception> exceptionHandler = null)
{
if (action == null) { throw new ArgumentNullException(nameof(action)); }
var task = new Task(action);
Action<Task> handler = exceptionHandler != null ?
new Action<Task>(t => exceptionHandler(t.Exception.GetBaseException())) :
DefaultExeptionHandler;
var continuation = task.ContinueWith(handler,
TaskContinuationOptions.ExecuteSynchronously
| TaskContinuationOptions.OnlyOnFaulted);
task.Start();
return continuation;
}
}
}
ที่นี่task
ไม่ได้ทำงานโดยตรง แต่จะถูกสร้างขึ้นมีการกำหนดความต่อเนื่องและจากนั้นงานจะถูกรันเพื่อกำจัดความเสี่ยงที่งานจะเสร็จสิ้นการดำเนินการ (หรือทิ้งข้อยกเว้นบางส่วน) ก่อนที่จะกำหนดความต่อเนื่อง
Run
วิธีการที่นี่ผลตอบแทนต่อเนื่องTask
ดังนั้นผมสามารถที่จะเขียนการทดสอบหน่วยการทำให้แน่ใจว่าการดำเนินการเสร็จสมบูรณ์แล้ว คุณสามารถเพิกเฉยได้อย่างปลอดภัยในการใช้งานของคุณ